#![forbid(unsafe_code, future_incompatible)]
#![forbid(rust_2018_idioms, rust_2018_compatibility)]
#![deny(missing_debug_implementations, bad_style)]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
mod error;
pub use crate::error::{Error, ErrorKind, Result};
use failure::ResultExt;
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Encoding {
Gzip,
Deflate,
Brotli,
Zstd,
Identity,
}
impl Encoding {
fn parse(s: &str) -> Result<Option<Encoding>> {
match s {
"gzip" => Ok(Some(Encoding::Gzip)),
"deflate" => Ok(Some(Encoding::Deflate)),
"br" => Ok(Some(Encoding::Brotli)),
"zstd" => Ok(Some(Encoding::Zstd)),
"identity" => Ok(Some(Encoding::Identity)),
"*" => Ok(None),
_ => Err(ErrorKind::UnknownEncoding)?,
}
}
pub fn to_header_value(self) -> HeaderValue {
match self {
Encoding::Gzip => HeaderValue::from_str("gzip").unwrap(),
Encoding::Deflate => HeaderValue::from_str("deflate").unwrap(),
Encoding::Brotli => HeaderValue::from_str("br").unwrap(),
Encoding::Zstd => HeaderValue::from_str("zstd").unwrap(),
Encoding::Identity => HeaderValue::from_str("identity").unwrap(),
}
}
}
pub fn parse(headers: &HeaderMap) -> Result<Option<Encoding>> {
let mut preferred_encoding = None;
let mut max_qval = 0.0;
for (encoding, qval) in encodings(headers)? {
if (qval - 1.0f32).abs() < 0.01 {
preferred_encoding = encoding;
break;
} else if qval > max_qval {
preferred_encoding = encoding;
max_qval = qval;
}
}
Ok(preferred_encoding)
}
pub fn encodings(headers: &HeaderMap) -> Result<Vec<(Option<Encoding>, f32)>> {
headers
.get_all(ACCEPT_ENCODING)
.iter()
.map(|hval| {
hval.to_str()
.context(ErrorKind::InvalidEncoding)
.map_err(Into::into)
})
.collect::<Result<Vec<&str>>>()?
.iter()
.flat_map(|s| s.split(',').map(str::trim))
.filter_map(|v| {
let mut v = v.splitn(2, ";q=");
let encoding = match Encoding::parse(v.next().unwrap()) {
Ok(encoding) => encoding,
Err(_) => return None, };
let qval = if let Some(qval) = v.next() {
let qval = match qval.parse::<f32>() {
Ok(f) => f,
Err(_) => return Some(Err(ErrorKind::InvalidEncoding)),
};
if qval > 1.0 {
return Some(Err(ErrorKind::InvalidEncoding)); }
qval
} else {
1.0f32
};
Some(Ok((encoding, qval)))
})
.map(|v| v.map_err(std::convert::Into::into))
.collect::<Result<Vec<(Option<Encoding>, f32)>>>()
}