useful headers for rocket

This commit is contained in:
Dan Dumont 2024-04-04 11:34:05 -04:00
parent 8614a7fece
commit c2251a4b48
No known key found for this signature in database
GPG Key ID: C1D78EE92127DA19
9 changed files with 1082 additions and 1 deletions

View File

@ -0,0 +1,369 @@
use std::borrow::Cow;
use std::ops::Deref;
use std::str::FromStr;
use std::fmt;
use crate::{ContentCoding, Header};
use crate::parse::parse_accept_encoding;
/// The HTTP Accept-Encoding header.
///
/// An `AcceptEncoding` header is composed of zero or more content codings, each of which
/// may have an optional weight value (a [`QContentCoding`]). The header is sent by
/// an HTTP client to describe the formats it accepts as well as the order in
/// which it prefers different formats.
///
/// # Usage
///
/// The Accept-Encoding header of an incoming request can be retrieved via the
/// [`Request::accept_encoding()`] method. The [`preferred()`] method can be used to
/// retrieve the client's preferred content coding.
///
/// [`Request::accept_encoding()`]: rocket::Request::accept_encoding()
/// [`preferred()`]: AcceptEncoding::preferred()
///
/// An `AcceptEncoding` type with a single, common content coding can be easily constructed
/// via provided associated constants.
///
/// ## Example
///
/// Construct an `AcceptEncoding` header with a single `gzip` content coding:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::AcceptEncoding;
///
/// # #[allow(unused_variables)]
/// let accept_gzip = AcceptEncoding::GZIP;
/// ```
///
/// # Header
///
/// `AcceptEncoding` implements `Into<Header>`. As such, it can be used in any context
/// where an `Into<Header>` is expected:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::AcceptEncoding;
/// use rocket::response::Response;
///
/// let response = Response::build().header(AcceptEncoding::GZIP).finalize();
/// ```
#[derive(Debug, Clone)]
pub struct AcceptEncoding(pub(crate) Cow<'static, [QContentCoding]>);
/// A `ContentCoding` with an associated quality value.
#[derive(Debug, Clone, PartialEq)]
pub struct QContentCoding(pub ContentCoding, pub Option<f32>);
macro_rules! accept_encoding_constructor {
($($name:ident ($check:ident): $str:expr, $c:expr,)+) => {
$(
#[doc="An `AcceptEncoding` header with the single content coding for"]
#[doc=concat!("**", $str, "**: ", "_", $c, "_")]
#[allow(non_upper_case_globals)]
pub const $name: AcceptEncoding = AcceptEncoding({
const INNER: &[QContentCoding] = &[QContentCoding(ContentCoding::$name, None)];
Cow::Borrowed(INNER)
});
)+
};
}
impl AcceptEncoding {
/// Constructs a new `AcceptEncoding` header from one or more media types.
///
/// The `items` parameter may be of type `QContentCoding`, `[QContentCoding]`,
/// `&[QContentCoding]` or `Vec<QContentCoding>`. To prevent additional allocations,
/// prefer to provide inputs of type `QContentCoding`, `[QContentCoding]`, or
/// `Vec<QContentCoding>`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{QContentCoding, ContentCoding, AcceptEncoding};
///
/// // Construct an `Accept` via a `Vec<QMediaType>`.
/// let gzip_then_deflate = vec![ContentCoding::GZIP, ContentCoding::DEFLATE];
/// let accept = AcceptEncoding::new(gzip_then_deflate);
/// assert_eq!(accept.preferred().media_type(), &ContentCoding::GZIP);
///
/// // Construct an `Accept` via an `[QMediaType]`.
/// let accept = Accept::new([MediaType::JSON.into(), MediaType::HTML.into()]);
/// assert_eq!(accept.preferred().media_type(), &MediaType::JSON);
///
/// // Construct an `Accept` via a `QMediaType`.
/// let accept = Accept::new(QMediaType(MediaType::JSON, None));
/// assert_eq!(accept.preferred().media_type(), &MediaType::JSON);
/// ```
#[inline(always)]
pub fn new<T: IntoIterator<Item = M>, M: Into<QContentCoding>>(items: T) -> AcceptEncoding {
AcceptEncoding(items.into_iter().map(|v| v.into()).collect())
}
// TODO: Implement this.
// #[inline(always)]
// pub fn add<M: Into<QContentCoding>>(&mut self, content_coding: M) {
// self.0.push(content_coding.into());
// }
/// Retrieve the client's preferred content coding. This method follows [RFC
/// 7231 5.3.4]. If the list of content codings is empty, this method returns a
/// content coding of any with no quality value: (`*`).
///
/// [RFC 7231 5.3.4]: https://tools.ietf.org/html/rfc7231#section-5.3.4
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{QContentCoding, ContentCoding, AcceptEncoding};
///
/// let qcontent_codings = vec![
/// QContentCoding(MediaType::DEFLATE, Some(0.3)),
/// QContentCoding(MediaType::GZIP, Some(0.9)),
/// ];
///
/// let accept = AcceptEncoding::new(qcontent_codings);
/// assert_eq!(accept.preferred().content_coding(), &MediaType::GZIP);
/// ```
pub fn preferred(&self) -> &QContentCoding {
static ANY: QContentCoding = QContentCoding(ContentCoding::Any, None);
// See https://tools.ietf.org/html/rfc7231#section-5.3.4.
let mut all = self.iter();
let mut preferred = all.next().unwrap_or(&ANY);
for content_coding in all {
if content_coding.weight().is_none() && preferred.weight().is_some() {
// Content coding without a `q` parameter are preferred.
preferred = content_coding;
} else if content_coding.weight_or(0.0) > preferred.weight_or(1.0) {
// Prefer content coding with a greater weight, but if one doesn't
// have a weight, prefer the one we already have.
preferred = content_coding;
}
}
preferred
}
/// Retrieve the first media type in `self`, if any.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{QContentCoding, ContentCoding, AcceptEncoding};
///
/// let accept_encoding = AcceptEncoding::new(QContentCoding(ContentCoding::GZIP, None));
/// assert_eq!(accept_encoding.first(), Some(&ContentCoding::GZIP.into()));
/// ```
#[inline(always)]
pub fn first(&self) -> Option<&QContentCoding> {
self.iter().next()
}
/// Returns an iterator over all of the (quality) media types in `self`.
/// Media types are returned in the order in which they appear in the
/// header.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{QContentCoding, ContentCoding, AcceptEncoding};
///
/// let qcontent_codings = vec![
/// QContentCoding(MediaType::DEFLATE, Some(0.3))
/// QContentCoding(MediaType::GZIP, Some(0.9)),
/// ];
///
/// let accept_encoding = AcceptEncoding::new(qcontent_codings.clone());
///
/// let mut iter = accept.iter();
/// assert_eq!(iter.next(), Some(&qcontent_codings[0]));
/// assert_eq!(iter.next(), Some(&qcontent_codings[1]));
/// assert_eq!(iter.next(), None);
/// ```
#[inline(always)]
pub fn iter(&self) -> impl Iterator<Item=&'_ QContentCoding> + '_ {
self.0.iter()
}
/// Returns an iterator over all of the (bare) media types in `self`. Media
/// types are returned in the order in which they appear in the header.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{QMediaType, MediaType, Accept};
///
/// let qmedia_types = vec![
/// QMediaType(MediaType::JSON, Some(0.3)),
/// QMediaType(MediaType::HTML, Some(0.9))
/// ];
///
/// let accept = Accept::new(qmedia_types.clone());
///
/// let mut iter = accept.media_types();
/// assert_eq!(iter.next(), Some(qmedia_types[0].media_type()));
/// assert_eq!(iter.next(), Some(qmedia_types[1].media_type()));
/// assert_eq!(iter.next(), None);
/// ```
#[inline(always)]
pub fn content_codings(&self) -> impl Iterator<Item=&'_ ContentCoding> + '_ {
self.iter().map(|weighted_cc| weighted_cc.content_coding())
}
known_content_codings!(accept_encoding_constructor);
}
impl<T: IntoIterator<Item = ContentCoding>> From<T> for AcceptEncoding {
#[inline(always)]
fn from(items: T) -> AcceptEncoding {
AcceptEncoding::new(items.into_iter().map(QContentCoding::from))
}
}
impl PartialEq for AcceptEncoding {
fn eq(&self, other: &AcceptEncoding) -> bool {
self.iter().eq(other.iter())
}
}
impl fmt::Display for AcceptEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, content_coding) in self.iter().enumerate() {
if i >= 1 {
write!(f, ", {}", content_coding.0)?;
} else {
write!(f, "{}", content_coding.0)?;
}
}
Ok(())
}
}
impl FromStr for AcceptEncoding {
// Ideally we'd return a `ParseError`, but that requires a lifetime.
type Err = String;
#[inline]
fn from_str(raw: &str) -> Result<AcceptEncoding, String> {
parse_accept_encoding(raw).map_err(|e| e.to_string())
}
}
/// Creates a new `Header` with name `Accept-Encoding` and the value set to the HTTP
/// rendering of this `Accept` header.
impl From<AcceptEncoding> for Header<'static> {
#[inline(always)]
fn from(val: AcceptEncoding) -> Self {
Header::new("Accept-Encoding", val.to_string())
}
}
impl QContentCoding {
/// Retrieve the weight of the media type, if there is any.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{ContentCoding, QContentCoding};
///
/// let q_coding = QContentCoding(ContentCoding::GZIP, Some(0.3));
/// assert_eq!(q_coding.weight(), Some(0.3));
/// ```
#[inline(always)]
pub fn weight(&self) -> Option<f32> {
self.1
}
/// Retrieve the weight of the media type or a given default value.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{ContentCoding, QContentCoding};
///
/// let q_coding = QContentCoding(ContentCoding::GZIP, Some(0.3));
/// assert_eq!(q_coding.weight_or(0.9), 0.3);
///
/// let q_coding = QContentCoding(ContentCoding::GZIP, None);
/// assert_eq!(q_coding.weight_or(0.9), 0.9);
/// ```
#[inline(always)]
pub fn weight_or(&self, default: f32) -> f32 {
self.1.unwrap_or(default)
}
/// Borrow the internal `MediaType`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{ContentCoding, QContentCoding};
///
/// let q_coding = QContentCoding(ContentCoding::GZIP, Some(0.3));
/// assert_eq!(q_coding.content_coding(), &ContentCoding::GZIP);
/// ```
#[inline(always)]
pub fn content_coding(&self) -> &ContentCoding {
&self.0
}
}
impl From<ContentCoding> for QContentCoding {
#[inline(always)]
fn from(content_coding: ContentCoding) -> QContentCoding {
QContentCoding(content_coding, None)
}
}
impl Deref for QContentCoding {
type Target = ContentCoding;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod test {
use crate::{AcceptEncoding, ContentCoding};
#[track_caller]
fn assert_preference(string: &str, expect: &str) {
let ae: AcceptEncoding = string.parse().expect("accept_encoding string parse");
let expected: ContentCoding = expect.parse().expect("content coding parse");
let preferred = ae.preferred();
let actual = preferred.content_coding();
if *actual != expected {
panic!("mismatch for {}: expected {}, got {}", string, expected, actual)
}
}
#[test]
fn test_preferred() {
assert_preference("deflate", "deflate");
assert_preference("gzip, deflate", "gzip");
assert_preference("deflate; q=0.1, gzip", "gzip");
assert_preference("gzip; q=1, gzip", "gzip");
assert_preference("gzip, deflate; q=1", "gzip");
assert_preference("deflate; q=1, gzip", "gzip");
assert_preference("gzip; q=0.1, gzip; q=0.2", "gzip; q=0.2");
assert_preference("rar; q=0.1, compress; q=0.2", "compress; q=0.2");
assert_preference("rar; q=0.5, compress; q=0.2", "rar; q=0.5");
assert_preference("rar; q=0.5, compress; q=0.2, nonsense", "nonsense");
}
}

View File

@ -0,0 +1,309 @@
use core::f32;
use std::borrow::Cow;
use std::str::FromStr;
use std::fmt;
use std::hash::{Hash, Hasher};
use crate::uncased::UncasedStr;
use crate::parse::{Indexed, IndexedStr, parse_content_coding};
use crate::Source;
/// An HTTP content coding.
///
/// # Usage
///
/// A `ContentCoding` should rarely be used directly. Instead, one is typically used
/// indirectly via types like [`Accept-Encoding`](crate::Accept-Encoding) and
/// [`ContentEncoding`](crate::ContentEncoding), which internally contain `ContentCoding`s.
/// Nonetheless, a `ContentCoding` can be created via the [`ContentCoding::new()`]
/// and [`ContentCoding::with_weight()`].
/// The preferred method, however, is to create a `ContentCoding` via an associated
/// constant.
///
/// ## Example
///
/// A content coding of `gzip` can be instantiated via the
/// [`ContentCoding::GZIP`] constant:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentCoding;
///
/// let gzip = ContentCoding::GZIP;
/// assert_eq!(gzip.coding(), "gzip");
///
/// let gzip = ContentCoding::new("gzip");
/// assert_eq!(ContentCoding::GZIP, gzip);
/// ```
///
/// # Comparison and Hashing
///
/// The `PartialEq` and `Hash` implementations for `ContentCoding` _do not_ take
/// into account parameters. This means that a content coding of `gzip` is
/// equal to a content coding of `gzip; q=1`, for instance. This is
/// typically the comparison that is desired.
///
/// If an exact comparison is desired that takes into account parameters, the
/// [`exact_eq()`](ContentCoding::exact_eq()) method can be used.
#[derive(Debug, Clone)]
pub struct ContentCoding {
/// InitCell for the entire content codding string.
pub(crate) source: Source,
/// The top-level type.
pub(crate) coding: IndexedStr<'static>,
/// The parameters, if any.
pub(crate) weight: Option<f32>,
}
macro_rules! content_codings {
// ($($name:ident ($check:ident): $str:expr, $t:expr, $s:expr $(; $k:expr => $v:expr)*,)+)
($($name:ident ($check:ident): $str:expr, $c:expr,)+) => {
$(
/// Content Coding for
#[doc = concat!("**", $str, "**: ")]
#[doc = concat!("`", $c, "`")]
#[allow(non_upper_case_globals)]
pub const $name: ContentCoding = ContentCoding::new_known(
$c,
$c,
None,
);
)+
/// Returns `true` if this ContentCoding is known to Rocket. In other words,
/// returns `true` if there is an associated constant for `self`.
pub fn is_known(&self) -> bool {
if let Source::Known(_) = self.source {
return true;
}
$(if self.$check() { return true })+
false
}
$(
/// Returns `true` if the top-level and sublevel types of
/// `self` are the same as those of
#[doc = concat!("`ContentCoding::", stringify!($name), "`, ")]
/// i.e
#[doc = concat!("`", $c, "`.")]
#[inline(always)]
pub fn $check(&self) -> bool {
*self == ContentCoding::$name
}
)+
}
}
impl ContentCoding {
/// Creates a new `ContentCoding` for `coding`.
/// This should _only_ be used to construct uncommon or custom content codings.
/// Use an associated constant for everything else.
///
/// # Example
///
/// Create a custom `rar` content coding:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentCoding;
///
/// let custom = ContentCoding::new("rar");
/// assert_eq!(custom.coding(), "rar");
/// ```
#[inline]
pub fn new<C>(coding: C) -> ContentCoding
where C: Into<Cow<'static, str>>
{
ContentCoding {
source: Source::None,
coding: Indexed::Concrete(coding.into()),
weight: None,
}
}
/// Sets the weight `weight` on `self`.
///
/// # Example
///
/// Create a custom `rar; q=1` content coding:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentCoding;
///
/// let id = ContentCoding::new("rar").with_weight(1);
/// assert_eq!(id.to_string(), "rar; q=1".to_string());
/// ```
pub fn with_weight(mut self, p: f32) -> ContentCoding
{
self.weight = Some(p);
self
}
/// A `const` variant of [`ContentCoding::with_params()`]. Creates a new
/// `ContentCoding` with coding `coding`, and weight
/// `weight`, which may be empty.
///
/// # Example
///
/// Create a custom `rar` content coding:
///
/// ```rust
/// use rocket::http::ContentCoding;
///
/// let custom = ContentCoding::const_new("rar", None);
/// assert_eq!(custom.coding(), "rar");
/// assert_eq!(custom.weight(), None);
/// ```
#[inline]
pub const fn const_new(
coding: &'static str,
weight: Option<f32>,
) -> ContentCoding {
ContentCoding {
source: Source::None,
coding: Indexed::Concrete(Cow::Borrowed(coding)),
weight: weight,
}
}
#[inline]
pub(crate) const fn new_known(
source: &'static str,
coding: &'static str,
weight: Option<f32>,
) -> ContentCoding {
ContentCoding {
source: Source::Known(source),
coding: Indexed::Concrete(Cow::Borrowed(coding)),
weight: weight,
}
}
pub(crate) fn known_source(&self) -> Option<&'static str> {
match self.source {
Source::Known(string) => Some(string),
Source::Custom(Cow::Borrowed(string)) => Some(string),
_ => None
}
}
/// Returns the coding for this ContentCoding. The return type,
/// `UncasedStr`, has caseless equality comparison and hashing.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentCoding;
///
/// let gzip = ContentCoding::GZIP;
/// assert_eq!(gzip.coding(), "gzip");
/// assert_eq!(gzip.top(), "GZIP");
/// assert_eq!(gzip.top(), "Gzip");
/// ```
#[inline]
pub fn coding(&self) -> &UncasedStr {
self.coding.from_source(self.source.as_str()).into()
}
/// Compares `self` with `other` and returns `true` if `self` and `other`
/// are exactly equal to each other, including with respect to their
/// weight.
///
/// This is different from the `PartialEq` implementation in that it
/// considers parameters. In particular, `Eq` implies `PartialEq` but
/// `PartialEq` does not imply `Eq`. That is, if `PartialEq` returns false,
/// this function is guaranteed to return false. Similarly, if `exact_eq`
/// returns `true`, `PartialEq` is guaranteed to return true. However, if
/// `PartialEq` returns `true`, `exact_eq` function may or may not return
/// `true`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentCoding;
///
/// let gzip = ContentCoding::GZIP;
/// let gzip2 = ContentCoding::new("gzip").with_weight(1);
/// let just_plain = ContentCoding::new("gzip");
///
/// // The `PartialEq` implementation doesn't consider parameters.
/// assert!(plain == just_plain);
/// assert!(just_plain == plain2);
/// assert!(plain == plain2);
///
/// // While `exact_eq` does.
/// assert!(plain.exact_eq(&just_plain));
/// assert!(!plain2.exact_eq(&just_plain));
/// assert!(!plain.exact_eq(&plain2));
/// ```
pub fn exact_eq(&self, other: &ContentCoding) -> bool {
self == other && self.weight().eq(other.weight())
}
/// Returns the weight content coding.
///
/// # Example
///
/// The `ContentCoding::GZIP` type has no specified weight:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentCoding;
///
/// let gzip = ContentCoding::GZIP;
/// let weight = gzip.weight();
/// assert_eq!(weight, None);
/// ```
#[inline]
pub fn weight(&self) -> &Option<f32> {
&self.weight
}
known_content_codings!(content_codings);
}
impl FromStr for ContentCoding {
// Ideally we'd return a `ParseError`, but that requires a lifetime.
type Err = String;
#[inline]
fn from_str(raw: &str) -> Result<ContentCoding, String> {
parse_content_coding(raw).map_err(|e| e.to_string())
}
}
impl PartialEq for ContentCoding {
#[inline(always)]
fn eq(&self, other: &ContentCoding) -> bool {
self.coding() == other.coding()
}
}
impl Eq for ContentCoding { }
impl Hash for ContentCoding {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.coding().hash(state);
}
}
impl fmt::Display for ContentCoding {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(src) = self.known_source() {
src.fmt(f)
} else {
write!(f, "{}", self.coding())?;
if let Some(weight) = self.weight() {
write!(f, "; q={}", weight)?;
}
Ok(())
}
}
}

View File

@ -0,0 +1,187 @@
use std::borrow::Cow;
use std::ops::Deref;
use std::str::FromStr;
use std::fmt;
use crate::header::Header;
use crate::ContentCoding;
/// Representation of HTTP Content-Encoding.
///
/// # Usage
///
/// `ContentEncoding`s should rarely be created directly. Instead, an associated
/// constant should be used; one is declared for most commonly used content
/// types.
///
/// ## Example
///
/// A Content-Encoding of `gzip` can be instantiated via the
/// `GZIP` constant:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentEncoding;
///
/// # #[allow(unused_variables)]
/// let html = ContentEncoding::GZIP;
/// ```
///
/// # Header
///
/// `ContentEncoding` implements `Into<Header>`. As such, it can be used in any
/// context where an `Into<Header>` is expected:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentEncoding;
/// use rocket::response::Response;
///
/// let response = Response::build().header(ContentEncoding::GZIP).finalize();
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ContentEncoding(pub ContentCoding);
macro_rules! content_encodings {
($($name:ident ($check:ident): $str:expr, $c:expr,)+) => {
$(
/// Content Encoding for
#[doc = concat!("**", $str, "**: ")]
#[doc = concat!("`", $c, "`")]
#[allow(non_upper_case_globals)]
pub const $name: ContentEncoding = ContentEncoding(ContentCoding::$name);
)+
}}
impl ContentEncoding {
/// Creates a new `ContentEncoding` with `coding`.
/// This should _only_ be used to construct uncommon or custom content
/// types. Use an associated constant for everything else.
///
/// # Example
///
/// Create a custom `foo` content encoding:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentEncoding;
///
/// let custom = ContentEncoding::new("foo");
/// assert_eq!(custom.content_coding(), "foo");
/// ```
#[inline(always)]
pub fn new<S>(coding: S) -> ContentEncoding
where S: Into<Cow<'static, str>>
{
ContentEncoding(ContentCoding::new(coding))
}
/// Borrows the inner `ContentCoding` of `self`.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::{ContentEncoding, ContentCoding};
///
/// let http = ContentEncoding::GZIP;
/// let content_coding = http.content_coding();
/// ```
#[inline(always)]
pub fn content_coding(&self) -> &ContentCoding {
&self.0
}
known_content_codings!(content_encodings);
}
impl Default for ContentEncoding {
/// Returns a ContentEncoding of `Any`, or `*`.
#[inline(always)]
fn default() -> ContentEncoding {
ContentEncoding::Any
}
}
impl Deref for ContentEncoding {
type Target = ContentCoding;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ContentEncoding {
type Err = String;
/// Parses a `ContentEncoding` from a given Content-Encoding header value.
///
/// # Examples
///
/// Parsing a `gzip`:
///
/// ```rust
/// # extern crate rocket;
/// use std::str::FromStr;
/// use rocket::http::ContentEncoding;
///
/// let gzip = ContentEncoding::from_str("gzip").unwrap();
/// assert!(gzip.is_known());
/// assert_eq!(gzip, ContentEncoding::GZIP);
/// ```
///
/// Parsing an invalid Content-Encoding value:
///
/// ```rust
/// # extern crate rocket;
/// use std::str::FromStr;
/// use rocket::http::ContentEncoding;
///
/// let custom = ContentEncoding::from_str("12ec/.322r");
/// assert!(custom.is_err());
/// ```
#[inline(always)]
fn from_str(raw: &str) -> Result<ContentEncoding, String> {
ContentCoding::from_str(raw).map(ContentEncoding)
}
}
impl From<ContentCoding> for ContentEncoding {
fn from(content_coding: ContentCoding) -> Self {
ContentEncoding(content_coding)
}
}
impl fmt::Display for ContentEncoding {
/// Formats the ContentEncoding as an HTTP Content-Encoding value.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentEncoding;
///
/// let cc = format!("{}", ContentEncoding::GZIP);
/// assert_eq!(cc, "gzip");
/// ```
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Creates a new `Header` with name `Content-Encoding` and the value set to the
/// HTTP rendering of this Content-Encoding.
impl From<ContentEncoding> for Header<'static> {
#[inline(always)]
fn from(content_encoding: ContentEncoding) -> Self {
if let Some(src) = content_encoding.known_source() {
Header::new("Content-Encoding", src)
} else {
Header::new("Content-Encoding", content_encoding.to_string())
}
}
}

View File

@ -0,0 +1,10 @@
macro_rules! known_content_codings {
($cont:ident) => ($cont! {
Any (is_any): "any content coding", "*",
// BR (is_br): "Brotli Compressed Data Format", "br",
// COMPRESS (is_compress): "UNIX \"compress\" data format", "compress",
// DEFLATE (is_deflate): "\"deflate\" compressed data inside the \"zlib\" data format", "deflate",
GZIP (is_gzip): "GZIP file format", "gzip",
IDENTITY (is_identity): "Reserved", "identity",
})
}

View File

@ -621,7 +621,7 @@ impl Extend<(IndexedStr<'static>, IndexedStr<'static>)> for MediaParams {
impl Source { impl Source {
#[inline] #[inline]
fn as_str(&self) -> Option<&str> { pub(crate) fn as_str(&self) -> Option<&str> {
match *self { match *self {
Source::Known(s) => Some(s), Source::Known(s) => Some(s),
Source::Custom(ref s) => Some(s.borrow()), Source::Custom(ref s) => Some(s.borrow()),

View File

@ -1,13 +1,21 @@
#[macro_use] #[macro_use]
mod known_media_types; mod known_media_types;
#[macro_use]
mod known_content_codings;
mod media_type; mod media_type;
mod content_coding;
mod content_type; mod content_type;
mod accept; mod accept;
mod accept_encoding;
mod content_encoding;
mod header; mod header;
mod proxy_proto; mod proxy_proto;
pub use self::content_type::ContentType; pub use self::content_type::ContentType;
pub use self::content_encoding::ContentEncoding;
pub use self::accept::{Accept, QMediaType}; pub use self::accept::{Accept, QMediaType};
pub use self::accept_encoding::{AcceptEncoding, QContentCoding};
pub use self::content_coding::ContentCoding;
pub use self::media_type::MediaType; pub use self::media_type::MediaType;
pub use self::header::{Header, HeaderMap}; pub use self::header::{Header, HeaderMap};
pub use self::proxy_proto::ProxyProto; pub use self::proxy_proto::ProxyProto;

View File

@ -0,0 +1,75 @@
use pear::macros::parser;
use pear::combinators::{series, surrounded};
use crate::{AcceptEncoding, QContentCoding};
use crate::parse::checkers::is_whitespace;
use crate::parse::content_coding::content_coding;
type Input<'a> = pear::input::Pear<pear::input::Cursor<&'a str>>;
type Result<'a, T> = pear::input::Result<T, Input<'a>>;
#[parser]
fn weighted_content_coding<'a>(input: &mut Input<'a>) -> Result<'a, QContentCoding> {
let content_coding = content_coding()?;
let weight = match content_coding.weight() {
Some(v) => Some(*v),
_ => None
};
QContentCoding(content_coding, weight)
}
#[parser]
fn accept_encoding<'a>(input: &mut Input<'a>) -> Result<'a, AcceptEncoding> {
let vec = series(|i| surrounded(i, weighted_content_coding, is_whitespace), ',')?;
AcceptEncoding(std::borrow::Cow::Owned(vec))
}
pub fn parse_accept_encoding(input: &str) -> Result<'_, AcceptEncoding> {
parse!(accept_encoding: Input::new(input))
}
#[cfg(test)]
mod test {
use crate::ContentCoding;
use super::parse_accept_encoding;
macro_rules! assert_parse {
($string:expr) => ({
match parse_accept_encoding($string) {
Ok(ae) => ae,
Err(e) => panic!("{:?} failed to parse: {}", $string, e)
}
});
}
macro_rules! assert_parse_eq {
($string:expr, [$($cc:expr),*]) => ({
let expected = vec![$($cc),*];
let result = assert_parse!($string);
for (i, wcc) in result.iter().enumerate() {
assert_eq!(wcc.content_coding(), &expected[i]);
}
});
}
#[test]
fn check_does_parse() {
assert_parse!("gzip");
assert_parse!("gzip; q=1");
assert_parse!("*, gzip; q=1.0, rar, deflate");
assert_parse!("rar, deflate");
assert_parse!("deflate;q=0.3, gzip;q=0.7, rar;q=0.4, *;q=0.5");
}
#[test]
fn check_parse_eq() {
assert_parse_eq!("gzip", [ContentCoding::GZIP]);
assert_parse_eq!("gzip, deflate",
[ContentCoding::GZIP, ContentCoding::new("deflate")]);
assert_parse_eq!("gzip; q=1, deflate",
[ContentCoding::GZIP, ContentCoding::new("deflate")]);
assert_parse_eq!("gzip, gzip; q=0.1, gzip; q=0.2",
[ContentCoding::GZIP, ContentCoding::GZIP, ContentCoding::GZIP]);
}
}

View File

@ -0,0 +1,119 @@
use std::borrow::Cow;
use pear::input::Extent;
use pear::macros::{parser, parse};
use pear::parsers::*;
use pear::combinators::surrounded;
use crate::header::{ContentCoding, Source};
use crate::parse::checkers::{is_valid_token, is_whitespace};
type Input<'a> = pear::input::Pear<pear::input::Cursor<&'a str>>;
type Result<'a, T> = pear::input::Result<T, Input<'a>>;
#[parser]
fn coding_param<'a>(input: &mut Input<'a>) -> Result<'a, Extent<&'a str>> {
let _ = (take_some_while_until(|c| matches!(c, 'Q' | 'q'), '=')?, eat('=')?).0;
let value = take_some_while_until(|c| matches!(c, '0'..='9' | '.'), ';')?;
value
}
#[parser]
pub fn content_coding<'a>(input: &mut Input<'a>) -> Result<'a, ContentCoding> {
let (coding, weight) = {
let coding = take_some_while_until(is_valid_token, ';')?;
let weight = match eat(input, ';') {
Ok(_) => surrounded(coding_param, is_whitespace)?,
Err(_) => Extent {start: 0, end: 0, values: ""},
};
(coding, weight)
};
let weight = match weight.len() {
len if len > 0 && len <= 5 => match weight.parse::<f32>().ok() {
Some(q) if q > 1. => parse_error!("q value must be <= 1")?,
Some(q) if q < 0. => parse_error!("q value must be > 0")?,
Some(q) => Some(q),
None => parse_error!("invalid content coding weight")?
},
_ => None,
};
ContentCoding {
weight: weight,
source: Source::Custom(Cow::Owned(input.start.to_string())),
coding: coding.into(),
}
}
pub fn parse_content_coding(input: &str) -> Result<'_, ContentCoding> {
parse!(content_coding: Input::new(input))
}
#[cfg(test)]
mod test {
use crate::ContentCoding;
use super::parse_content_coding;
macro_rules! assert_no_parse {
($string:expr) => ({
let result: Result<_, _> = parse_content_coding($string).into();
if result.is_ok() {
panic!("{:?} parsed unexpectedly.", $string)
}
});
}
macro_rules! assert_parse {
($string:expr) => ({
match parse_content_coding($string) {
Ok(content_coding) => content_coding,
Err(e) => panic!("{:?} failed to parse: {}", $string, e)
}
});
}
macro_rules! assert_parse_eq {
(@full $string:expr, $result:expr, $weight:expr) => ({
let result = assert_parse!($string);
assert_eq!(result, $result);
assert_eq!(*result.weight(), $weight);
});
(from: $string:expr, into: $result:expr)
=> (assert_parse_eq!(@full $string, $result, None));
(from: $string:expr, into: $result:expr, weight: $weight:literal)
=> (assert_parse_eq!(@full $string, $result, Some($weight)));
}
#[test]
fn check_does_parse() {
assert_parse!("*");
assert_parse!("rar");
assert_parse!("gzip");
assert_parse!("identity");
}
#[test]
fn check_parse_eq() {
assert_parse_eq!(from: "gzip", into: ContentCoding::GZIP);
assert_parse_eq!(from: "gzip; q=1", into: ContentCoding::GZIP, weight: 1f32);
assert_parse_eq!(from: "*", into: ContentCoding::Any);
assert_parse_eq!(from: "rar", into: ContentCoding::new("rar"));
}
#[test]
fn check_params_do_parse() {
assert_parse!("*; q=1");
}
#[test]
fn test_bad_parses() {
assert_no_parse!("*; q=1;");
assert_no_parse!("*; q=1; q=2");
}
}

View File

@ -1,10 +1,14 @@
mod media_type; mod media_type;
mod accept; mod accept;
mod accept_encoding;
mod content_coding;
mod checkers; mod checkers;
mod indexed; mod indexed;
pub use self::media_type::*; pub use self::media_type::*;
pub use self::accept::*; pub use self::accept::*;
pub use self::accept_encoding::*;
pub use self::content_coding::*;
pub mod uri; pub mod uri;