Something went wrong. Try again.
A public mirror for the whole atmosphere hubble.microcosm.blue
Something went wrong. Try again.
4.7 kB · 143 lines
Rust
at backup
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144//! `Content-Encoding` we know how to decode, plus the dispatch helpers and//! the `Accept-Encoding` header value we send.
use async_compression::tokio::bufread::{BrotliDecoder, DeflateDecoder, GzipDecoder, ZstdDecoder};use reqwest::header::{self, HeaderMap, ToStrError};use tokio::io::{AsyncRead, BufReader};
/// Encodings we ask PDSes to consider, in preference order. Sent as the/// `Accept-Encoding` header on `getRepo` requests.pub const ACCEPT_ENCODING: &str = "zstd, br, gzip, deflate";
#[derive(Debug, thiserror::Error)]pub enum RequestEncodingError { #[error("non-ASCII Content-Encoding header: {0}")] NonAsciiName(#[from] ToStrError), #[error("unsupported content-encoding: {0:?}")] Unsupported(String),}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub enum KnownEncoding { Brotli, Deflate, Gzip, Zstd,}
impl std::str::FromStr for KnownEncoding { type Err = RequestEncodingError; fn from_str(s: &str) -> Result<Self, Self::Err> { match s.trim().to_ascii_lowercase().as_str() { "br" => Ok(Self::Brotli), "deflate" => Ok(Self::Deflate), "gzip" | "x-gzip" => Ok(Self::Gzip), "zstd" => Ok(Self::Zstd), _ => Err(RequestEncodingError::Unsupported(s.to_string())), } }}
impl KnownEncoding { /// Read the `Content-Encoding` header (if present) and parse it. /// Returns `Ok(None)` when the header is absent. /// Errors on non-ASCII header bytes or an unrecognized encoding name. pub fn from_response_headers( headers: &HeaderMap, ) -> Result<Option<Self>, RequestEncodingError> { match headers.get(header::CONTENT_ENCODING) { None => Ok(None), Some(v) => { let s = v.to_str()?; Ok(Some(s.parse()?)) } } }
pub fn as_str(self) -> &'static str { match self { Self::Brotli => "br", Self::Deflate => "deflate", Self::Gzip => "gzip", Self::Zstd => "zstd", } }
/// Wrap `src` in this encoding's decoder. The decoder needs `AsyncBufRead`, /// so a `BufReader` is added internally — callers just pass the raw /// `AsyncRead`. pub fn decode<R>(self, src: R) -> Box<dyn AsyncRead + Unpin + Send> where R: AsyncRead + Unpin + Send + 'static, { let buf = BufReader::new(src); match self { Self::Brotli => Box::new(BrotliDecoder::new(buf)), Self::Deflate => Box::new(DeflateDecoder::new(buf)), Self::Gzip => Box::new(GzipDecoder::new(buf)), Self::Zstd => Box::new(ZstdDecoder::new(buf)), } }}
#[cfg(test)]mod tests { use super::*; use reqwest::header::HeaderValue;
#[test] fn from_response_headers_absent() { let headers = HeaderMap::new(); assert!(matches!( KnownEncoding::from_response_headers(&headers), Ok(None) )); }
#[test] fn from_response_headers_recognized() { for (raw, expected) in [ ("gzip", KnownEncoding::Gzip), ("x-gzip", KnownEncoding::Gzip), ("br", KnownEncoding::Brotli), ("zstd", KnownEncoding::Zstd), ("deflate", KnownEncoding::Deflate), ("GZIP", KnownEncoding::Gzip), (" br ", KnownEncoding::Brotli), ] { let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_ENCODING, HeaderValue::from_static(raw)); assert_eq!( KnownEncoding::from_response_headers(&headers).unwrap(), Some(expected), "raw={raw:?}", ); } }
#[test] fn from_response_headers_unsupported_name() { for raw in ["identity", "snappy", "gzip, br"] { let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_ENCODING, HeaderValue::from_static(raw)); let err = KnownEncoding::from_response_headers(&headers).unwrap_err(); assert!( matches!(err, RequestEncodingError::Unsupported(_)), "raw={raw:?} got {err:?}", ); } }
#[test] fn from_response_headers_non_ascii() { let mut headers = HeaderMap::new(); let v = HeaderValue::from_bytes(&[0xff, 0xfe]) .expect("obs-text bytes are valid in HeaderValue"); headers.insert(header::CONTENT_ENCODING, v); let err = KnownEncoding::from_response_headers(&headers).unwrap_err(); assert!( matches!(err, RequestEncodingError::NonAsciiName(_)), "got {err:?}" ); }}