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}