mirror of https://github.com/rwf2/Rocket.git
useful headers for rocket
This commit is contained in:
parent
8614a7fece
commit
c2251a4b48
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
})
|
||||
}
|
|
@ -621,7 +621,7 @@ impl Extend<(IndexedStr<'static>, IndexedStr<'static>)> for MediaParams {
|
|||
|
||||
impl Source {
|
||||
#[inline]
|
||||
fn as_str(&self) -> Option<&str> {
|
||||
pub(crate) fn as_str(&self) -> Option<&str> {
|
||||
match *self {
|
||||
Source::Known(s) => Some(s),
|
||||
Source::Custom(ref s) => Some(s.borrow()),
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
#[macro_use]
|
||||
mod known_media_types;
|
||||
#[macro_use]
|
||||
mod known_content_codings;
|
||||
mod media_type;
|
||||
mod content_coding;
|
||||
mod content_type;
|
||||
mod accept;
|
||||
mod accept_encoding;
|
||||
mod content_encoding;
|
||||
mod header;
|
||||
mod proxy_proto;
|
||||
|
||||
pub use self::content_type::ContentType;
|
||||
pub use self::content_encoding::ContentEncoding;
|
||||
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::header::{Header, HeaderMap};
|
||||
pub use self::proxy_proto::ProxyProto;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
mod media_type;
|
||||
mod accept;
|
||||
mod accept_encoding;
|
||||
mod content_coding;
|
||||
mod checkers;
|
||||
mod indexed;
|
||||
|
||||
pub use self::media_type::*;
|
||||
pub use self::accept::*;
|
||||
pub use self::accept_encoding::*;
|
||||
pub use self::content_coding::*;
|
||||
|
||||
pub mod uri;
|
||||
|
||||
|
|
Loading…
Reference in New Issue