accept_encoding_fork/
lib.rs

1#![forbid(unsafe_code, future_incompatible)]
2#![forbid(rust_2018_idioms, rust_2018_compatibility)]
3#![deny(missing_debug_implementations, bad_style)]
4#![deny(missing_docs)]
5#![cfg_attr(test, deny(warnings))]
6
7//! ## Examples
8//! ```rust
9//! # use failure::Error;
10//! use accept_encoding::Encoding;
11//! use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
12//!
13//! # fn main () -> Result<(), failure::Error> {
14//! let mut headers = HeaderMap::new();
15//! headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip, deflate, br")?);
16//!
17//! let encoding = accept_encoding::parse(&headers)?;
18//! assert_eq!(encoding, Some(Encoding::Gzip));
19//! # Ok(())}
20//! ```
21//!
22//! ```rust
23//! # use failure::Error;
24//! use accept_encoding::Encoding;
25//! use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
26//!
27//! # fn main () -> Result<(), failure::Error> {
28//! let mut headers = HeaderMap::new();
29//! headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("gzip;q=0.5, deflate;q=0.9, br;q=1.0")?);
30//!
31//! let encoding = accept_encoding::parse(&headers)?;
32//! assert_eq!(encoding, Some(Encoding::Brotli));
33//! # Ok(())}
34//! ```
35
36mod error;
37
38pub use crate::error::{Error, ErrorKind, Result};
39use failure::ResultExt;
40use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
41
42/// Encodings to use.
43#[derive(Debug, Clone, Copy, Eq, PartialEq)]
44pub enum Encoding {
45    /// The Gzip encoding.
46    Gzip,
47    /// The Deflate encoding.
48    Deflate,
49    /// The Brotli encoding.
50    Brotli,
51    /// The Zstd encoding.
52    Zstd,
53    /// No encoding.
54    Identity,
55}
56
57impl Encoding {
58    /// Parses a given string into its corresponding encoding.
59    fn parse(s: &str) -> Result<Option<Encoding>> {
60        match s {
61            "gzip" => Ok(Some(Encoding::Gzip)),
62            "deflate" => Ok(Some(Encoding::Deflate)),
63            "br" => Ok(Some(Encoding::Brotli)),
64            "zstd" => Ok(Some(Encoding::Zstd)),
65            "identity" => Ok(Some(Encoding::Identity)),
66            "*" => Ok(None),
67            _ => Err(ErrorKind::UnknownEncoding)?,
68        }
69    }
70
71    /// Converts the encoding into its' corresponding header value.
72    pub fn to_header_value(self) -> HeaderValue {
73        match self {
74            Encoding::Gzip => HeaderValue::from_str("gzip").unwrap(),
75            Encoding::Deflate => HeaderValue::from_str("deflate").unwrap(),
76            Encoding::Brotli => HeaderValue::from_str("br").unwrap(),
77            Encoding::Zstd => HeaderValue::from_str("zstd").unwrap(),
78            Encoding::Identity => HeaderValue::from_str("identity").unwrap(),
79        }
80    }
81}
82
83/// Parse a set of HTTP headers into a single option yielding an `Encoding` that the client prefers.
84///
85/// If you're looking for an easy way to determine the best encoding for the client and support every [`Encoding`] listed, this is likely what you want.
86///
87/// Note that a result of `None` indicates there preference is expressed on which encoding to use.
88/// Either the `Accept-Encoding` header is not present, or `*` is set as the most preferred encoding.
89pub fn parse(headers: &HeaderMap) -> Result<Option<Encoding>> {
90    let mut preferred_encoding = None;
91    let mut max_qval = 0.0;
92
93    for (encoding, qval) in encodings(headers)? {
94        if (qval - 1.0f32).abs() < 0.01 {
95            preferred_encoding = encoding;
96            break;
97        } else if qval > max_qval {
98            preferred_encoding = encoding;
99            max_qval = qval;
100        }
101    }
102
103    Ok(preferred_encoding)
104}
105
106/// Parse a set of HTTP headers into a vector containing tuples of options containing encodings and their corresponding q-values.
107///
108/// If you're looking for more fine-grained control over what encoding to choose for the client, or if you don't support every [`Encoding`] listed, this is likely what you want.
109///
110/// Note that a result of `None` indicates there preference is expressed on which encoding to use.
111/// Either the `Accept-Encoding` header is not present, or `*` is set as the most preferred encoding.
112/// ## Examples
113/// ```rust
114/// # use failure::Error;
115/// use accept_encoding::Encoding;
116/// use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
117///
118/// # fn main () -> Result<(), failure::Error> {
119/// let mut headers = HeaderMap::new();
120/// headers.insert(ACCEPT_ENCODING, HeaderValue::from_str("zstd;q=1.0, deflate;q=0.8, br;q=0.9")?);
121///
122/// let encodings = accept_encoding::encodings(&headers)?;
123/// for (encoding, qval) in encodings {
124///     println!("{:?} {}", encoding, qval);
125/// }
126/// # Ok(())}
127/// ```
128pub fn encodings(headers: &HeaderMap) -> Result<Vec<(Option<Encoding>, f32)>> {
129    headers
130        .get_all(ACCEPT_ENCODING)
131        .iter()
132        .map(|hval| {
133            hval.to_str()
134                .context(ErrorKind::InvalidEncoding)
135                .map_err(Into::into)
136        })
137        .collect::<Result<Vec<&str>>>()?
138        .iter()
139        .flat_map(|s| s.split(',').map(str::trim))
140        .filter_map(|v| {
141            let mut v = v.splitn(2, ";q=");
142            let encoding = match Encoding::parse(v.next().unwrap()) {
143                Ok(encoding) => encoding,
144                Err(_) => return None, // ignore unknown encodings
145            };
146            let qval = if let Some(qval) = v.next() {
147                let qval = match qval.parse::<f32>() {
148                    Ok(f) => f,
149                    Err(_) => return Some(Err(ErrorKind::InvalidEncoding)),
150                };
151                if qval > 1.0 {
152                    return Some(Err(ErrorKind::InvalidEncoding)); // q-values over 1 are unacceptable
153                }
154                qval
155            } else {
156                1.0f32
157            };
158            Some(Ok((encoding, qval)))
159        })
160        .map(|v| v.map_err(std::convert::Into::into))
161        .collect::<Result<Vec<(Option<Encoding>, f32)>>>()
162}