rama_http_headers/encoding/
mod.rs

1//! Utility types and functions that can be used in context of encoding headers.
2
3use rama_utils::macros::match_ignore_ascii_case_str;
4use std::fmt;
5
6mod accept_encoding;
7pub use accept_encoding::AcceptEncoding;
8
9use super::specifier::{Quality, QualityValue};
10
11pub trait SupportedEncodings: Copy {
12    fn gzip(&self) -> bool;
13    fn deflate(&self) -> bool;
14    fn br(&self) -> bool;
15    fn zstd(&self) -> bool;
16}
17
18impl SupportedEncodings for bool {
19    fn gzip(&self) -> bool {
20        *self
21    }
22
23    fn deflate(&self) -> bool {
24        *self
25    }
26
27    fn br(&self) -> bool {
28        *self
29    }
30
31    fn zstd(&self) -> bool {
32        *self
33    }
34}
35
36#[derive(Copy, Clone, Debug, Ord, PartialOrd, PartialEq, Eq, Hash)]
37/// This enum's variants are ordered from least to most preferred.
38pub enum Encoding {
39    Identity,
40    Deflate,
41    Gzip,
42    Brotli,
43    Zstd,
44}
45
46impl fmt::Display for Encoding {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "{}", self.as_str())
49    }
50}
51
52impl From<Encoding> for rama_http_types::HeaderValue {
53    #[inline]
54    fn from(encoding: Encoding) -> Self {
55        rama_http_types::HeaderValue::from_static(encoding.as_str())
56    }
57}
58
59impl Encoding {
60    fn as_str(self) -> &'static str {
61        match self {
62            Encoding::Identity => "identity",
63            Encoding::Gzip => "gzip",
64            Encoding::Deflate => "deflate",
65            Encoding::Brotli => "br",
66            Encoding::Zstd => "zstd",
67        }
68    }
69
70    pub fn to_file_extension(self) -> Option<&'static std::ffi::OsStr> {
71        match self {
72            Encoding::Gzip => Some(std::ffi::OsStr::new(".gz")),
73            Encoding::Deflate => Some(std::ffi::OsStr::new(".zz")),
74            Encoding::Brotli => Some(std::ffi::OsStr::new(".br")),
75            Encoding::Zstd => Some(std::ffi::OsStr::new(".zst")),
76            Encoding::Identity => None,
77        }
78    }
79
80    fn parse(s: &str, supported_encoding: impl SupportedEncodings) -> Option<Encoding> {
81        match_ignore_ascii_case_str! {
82            match (s) {
83                "gzip" | "x-gzip" if supported_encoding.gzip() => Some(Encoding::Gzip),
84                "deflate" if supported_encoding.deflate() => Some(Encoding::Deflate),
85                "br" if supported_encoding.br() => Some(Encoding::Brotli),
86                "zstd" if supported_encoding.zstd() => Some(Encoding::Zstd),
87                "identity" => Some(Encoding::Identity),
88                _ => None,
89            }
90        }
91    }
92
93    pub fn maybe_from_content_encoding_header(
94        headers: &rama_http_types::HeaderMap,
95        supported_encoding: impl SupportedEncodings,
96    ) -> Option<Self> {
97        headers
98            .get(rama_http_types::header::CONTENT_ENCODING)
99            .and_then(|hval| hval.to_str().ok())
100            .and_then(|s| Encoding::parse(s, supported_encoding))
101    }
102
103    #[inline]
104    pub fn from_content_encoding_header(
105        headers: &rama_http_types::HeaderMap,
106        supported_encoding: impl SupportedEncodings,
107    ) -> Self {
108        Encoding::maybe_from_content_encoding_header(headers, supported_encoding)
109            .unwrap_or(Encoding::Identity)
110    }
111
112    pub fn maybe_from_accept_encoding_headers(
113        headers: &rama_http_types::HeaderMap,
114        supported_encoding: impl SupportedEncodings,
115    ) -> Option<Self> {
116        Encoding::maybe_preferred_encoding(parse_accept_encoding_headers(
117            headers,
118            supported_encoding,
119        ))
120    }
121
122    #[inline]
123    pub fn from_accept_encoding_headers(
124        headers: &rama_http_types::HeaderMap,
125        supported_encoding: impl SupportedEncodings,
126    ) -> Self {
127        Encoding::maybe_from_accept_encoding_headers(headers, supported_encoding)
128            .unwrap_or(Encoding::Identity)
129    }
130
131    pub fn maybe_preferred_encoding(
132        accepted_encodings: impl Iterator<Item = QualityValue<Encoding>>,
133    ) -> Option<Self> {
134        accepted_encodings
135            .filter(|qval| qval.quality.as_u16() > 0)
136            .max_by_key(|qval| (qval.quality, qval.value))
137            .map(|qval| qval.value)
138    }
139}
140
141/// based on https://github.com/http-rs/accept-encoding
142pub fn parse_accept_encoding_headers<'a>(
143    headers: &'a rama_http_types::HeaderMap,
144    supported_encoding: impl SupportedEncodings + 'a,
145) -> impl Iterator<Item = QualityValue<Encoding>> + 'a {
146    headers
147        .get_all(rama_http_types::header::ACCEPT_ENCODING)
148        .iter()
149        .filter_map(|hval| hval.to_str().ok())
150        .flat_map(|s| s.split(','))
151        .filter_map(move |v| {
152            let mut v = v.splitn(2, ';');
153
154            let encoding = match Encoding::parse(v.next().unwrap().trim(), supported_encoding) {
155                Some(encoding) => encoding,
156                None => return None, // ignore unknown encodings
157            };
158
159            let qval = if let Some(qval) = v.next() {
160                qval.trim().parse::<Quality>().ok()?
161            } else {
162                Quality::one()
163            };
164
165            Some(QualityValue::new(encoding, qval))
166        })
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[derive(Copy, Clone, Default)]
174    struct SupportedEncodingsAll;
175
176    impl SupportedEncodings for SupportedEncodingsAll {
177        fn gzip(&self) -> bool {
178            true
179        }
180
181        fn deflate(&self) -> bool {
182            true
183        }
184
185        fn br(&self) -> bool {
186            true
187        }
188
189        fn zstd(&self) -> bool {
190            true
191        }
192    }
193
194    #[test]
195    fn no_accept_encoding_header() {
196        let encoding = Encoding::from_accept_encoding_headers(
197            &rama_http_types::HeaderMap::new(),
198            SupportedEncodingsAll,
199        );
200        assert_eq!(Encoding::Identity, encoding);
201    }
202
203    #[test]
204    fn accept_encoding_header_single_encoding() {
205        let mut headers = rama_http_types::HeaderMap::new();
206        headers.append(
207            rama_http_types::header::ACCEPT_ENCODING,
208            rama_http_types::HeaderValue::from_static("gzip"),
209        );
210        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
211        assert_eq!(Encoding::Gzip, encoding);
212    }
213
214    #[test]
215    fn accept_encoding_header_two_encodings() {
216        let mut headers = rama_http_types::HeaderMap::new();
217        headers.append(
218            rama_http_types::header::ACCEPT_ENCODING,
219            rama_http_types::HeaderValue::from_static("gzip,br"),
220        );
221        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
222        assert_eq!(Encoding::Brotli, encoding);
223    }
224
225    #[test]
226    fn accept_encoding_header_gzip_x_gzip() {
227        let mut headers = rama_http_types::HeaderMap::new();
228        headers.append(
229            rama_http_types::header::ACCEPT_ENCODING,
230            rama_http_types::HeaderValue::from_static("gzip,x-gzip"),
231        );
232        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
233        assert_eq!(Encoding::Gzip, encoding);
234    }
235
236    #[test]
237    fn accept_encoding_header_x_gzip_deflate() {
238        let mut headers = rama_http_types::HeaderMap::new();
239        headers.append(
240            rama_http_types::header::ACCEPT_ENCODING,
241            rama_http_types::HeaderValue::from_static("deflate,x-gzip"),
242        );
243        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
244        assert_eq!(Encoding::Gzip, encoding);
245    }
246
247    #[test]
248    fn accept_encoding_header_three_encodings() {
249        let mut headers = rama_http_types::HeaderMap::new();
250        headers.append(
251            rama_http_types::header::ACCEPT_ENCODING,
252            rama_http_types::HeaderValue::from_static("gzip,deflate,br"),
253        );
254        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
255        assert_eq!(Encoding::Brotli, encoding);
256    }
257
258    #[test]
259    fn accept_encoding_header_two_encodings_with_one_qvalue() {
260        let mut headers = rama_http_types::HeaderMap::new();
261        headers.append(
262            rama_http_types::header::ACCEPT_ENCODING,
263            rama_http_types::HeaderValue::from_static("gzip;q=0.5,br"),
264        );
265        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
266        assert_eq!(Encoding::Brotli, encoding);
267    }
268
269    #[test]
270    fn accept_encoding_header_three_encodings_with_one_qvalue() {
271        let mut headers = rama_http_types::HeaderMap::new();
272        headers.append(
273            rama_http_types::header::ACCEPT_ENCODING,
274            rama_http_types::HeaderValue::from_static("gzip;q=0.5,deflate,br"),
275        );
276        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
277        assert_eq!(Encoding::Brotli, encoding);
278    }
279
280    #[test]
281    fn two_accept_encoding_headers_with_one_qvalue() {
282        let mut headers = rama_http_types::HeaderMap::new();
283        headers.append(
284            rama_http_types::header::ACCEPT_ENCODING,
285            rama_http_types::HeaderValue::from_static("gzip;q=0.5"),
286        );
287        headers.append(
288            rama_http_types::header::ACCEPT_ENCODING,
289            rama_http_types::HeaderValue::from_static("br"),
290        );
291        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
292        assert_eq!(Encoding::Brotli, encoding);
293    }
294
295    #[test]
296    fn two_accept_encoding_headers_three_encodings_with_one_qvalue() {
297        let mut headers = rama_http_types::HeaderMap::new();
298        headers.append(
299            rama_http_types::header::ACCEPT_ENCODING,
300            rama_http_types::HeaderValue::from_static("gzip;q=0.5,deflate"),
301        );
302        headers.append(
303            rama_http_types::header::ACCEPT_ENCODING,
304            rama_http_types::HeaderValue::from_static("br"),
305        );
306        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
307        assert_eq!(Encoding::Brotli, encoding);
308    }
309
310    #[test]
311    fn three_accept_encoding_headers_with_one_qvalue() {
312        let mut headers = rama_http_types::HeaderMap::new();
313        headers.append(
314            rama_http_types::header::ACCEPT_ENCODING,
315            rama_http_types::HeaderValue::from_static("gzip;q=0.5"),
316        );
317        headers.append(
318            rama_http_types::header::ACCEPT_ENCODING,
319            rama_http_types::HeaderValue::from_static("deflate"),
320        );
321        headers.append(
322            rama_http_types::header::ACCEPT_ENCODING,
323            rama_http_types::HeaderValue::from_static("br"),
324        );
325        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
326        assert_eq!(Encoding::Brotli, encoding);
327    }
328
329    #[test]
330    fn accept_encoding_header_two_encodings_with_two_qvalues() {
331        let mut headers = rama_http_types::HeaderMap::new();
332        headers.append(
333            rama_http_types::header::ACCEPT_ENCODING,
334            rama_http_types::HeaderValue::from_static("gzip;q=0.5,br;q=0.8"),
335        );
336        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
337        assert_eq!(Encoding::Brotli, encoding);
338
339        let mut headers = rama_http_types::HeaderMap::new();
340        headers.append(
341            rama_http_types::header::ACCEPT_ENCODING,
342            rama_http_types::HeaderValue::from_static("gzip;q=0.8,br;q=0.5"),
343        );
344        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
345        assert_eq!(Encoding::Gzip, encoding);
346
347        let mut headers = rama_http_types::HeaderMap::new();
348        headers.append(
349            rama_http_types::header::ACCEPT_ENCODING,
350            rama_http_types::HeaderValue::from_static("gzip;q=0.995,br;q=0.999"),
351        );
352        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
353        assert_eq!(Encoding::Brotli, encoding);
354    }
355
356    #[test]
357    fn accept_encoding_header_three_encodings_with_three_qvalues() {
358        let mut headers = rama_http_types::HeaderMap::new();
359        headers.append(
360            rama_http_types::header::ACCEPT_ENCODING,
361            rama_http_types::HeaderValue::from_static("gzip;q=0.5,deflate;q=0.6,br;q=0.8"),
362        );
363        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
364        assert_eq!(Encoding::Brotli, encoding);
365
366        let mut headers = rama_http_types::HeaderMap::new();
367        headers.append(
368            rama_http_types::header::ACCEPT_ENCODING,
369            rama_http_types::HeaderValue::from_static("gzip;q=0.8,deflate;q=0.6,br;q=0.5"),
370        );
371        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
372        assert_eq!(Encoding::Gzip, encoding);
373
374        let mut headers = rama_http_types::HeaderMap::new();
375        headers.append(
376            rama_http_types::header::ACCEPT_ENCODING,
377            rama_http_types::HeaderValue::from_static("gzip;q=0.6,deflate;q=0.8,br;q=0.5"),
378        );
379        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
380        assert_eq!(Encoding::Deflate, encoding);
381
382        let mut headers = rama_http_types::HeaderMap::new();
383        headers.append(
384            rama_http_types::header::ACCEPT_ENCODING,
385            rama_http_types::HeaderValue::from_static("gzip;q=0.995,deflate;q=0.997,br;q=0.999"),
386        );
387        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
388        assert_eq!(Encoding::Brotli, encoding);
389    }
390
391    #[test]
392    fn accept_encoding_header_invalid_encdoing() {
393        let mut headers = rama_http_types::HeaderMap::new();
394        headers.append(
395            rama_http_types::header::ACCEPT_ENCODING,
396            rama_http_types::HeaderValue::from_static("invalid,gzip"),
397        );
398        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
399        assert_eq!(Encoding::Gzip, encoding);
400    }
401
402    #[test]
403    fn accept_encoding_header_with_qvalue_zero() {
404        let mut headers = rama_http_types::HeaderMap::new();
405        headers.append(
406            rama_http_types::header::ACCEPT_ENCODING,
407            rama_http_types::HeaderValue::from_static("gzip;q=0"),
408        );
409        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
410        assert_eq!(Encoding::Identity, encoding);
411
412        let mut headers = rama_http_types::HeaderMap::new();
413        headers.append(
414            rama_http_types::header::ACCEPT_ENCODING,
415            rama_http_types::HeaderValue::from_static("gzip;q=0."),
416        );
417        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
418        assert_eq!(Encoding::Identity, encoding);
419
420        let mut headers = rama_http_types::HeaderMap::new();
421        headers.append(
422            rama_http_types::header::ACCEPT_ENCODING,
423            rama_http_types::HeaderValue::from_static("gzip;q=0,br;q=0.5"),
424        );
425        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
426        assert_eq!(Encoding::Brotli, encoding);
427    }
428
429    #[test]
430    fn accept_encoding_header_with_uppercase_letters() {
431        let mut headers = rama_http_types::HeaderMap::new();
432        headers.append(
433            rama_http_types::header::ACCEPT_ENCODING,
434            rama_http_types::HeaderValue::from_static("gZiP"),
435        );
436        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
437        assert_eq!(Encoding::Gzip, encoding);
438
439        let mut headers = rama_http_types::HeaderMap::new();
440        headers.append(
441            rama_http_types::header::ACCEPT_ENCODING,
442            rama_http_types::HeaderValue::from_static("gzip;q=0.5,br;Q=0.8"),
443        );
444        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
445        assert_eq!(Encoding::Brotli, encoding);
446    }
447
448    #[test]
449    fn accept_encoding_header_with_allowed_spaces() {
450        let mut headers = rama_http_types::HeaderMap::new();
451        headers.append(
452            rama_http_types::header::ACCEPT_ENCODING,
453            rama_http_types::HeaderValue::from_static(" gzip\t; q=0.5 ,\tbr ;\tq=0.8\t"),
454        );
455        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
456        assert_eq!(Encoding::Brotli, encoding);
457    }
458
459    #[test]
460    fn accept_encoding_header_with_invalid_spaces() {
461        let mut headers = rama_http_types::HeaderMap::new();
462        headers.append(
463            rama_http_types::header::ACCEPT_ENCODING,
464            rama_http_types::HeaderValue::from_static("gzip;q =0.5"),
465        );
466        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
467        assert_eq!(Encoding::Identity, encoding);
468
469        let mut headers = rama_http_types::HeaderMap::new();
470        headers.append(
471            rama_http_types::header::ACCEPT_ENCODING,
472            rama_http_types::HeaderValue::from_static("gzip;q= 0.5"),
473        );
474        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
475        assert_eq!(Encoding::Identity, encoding);
476    }
477
478    #[test]
479    fn accept_encoding_header_with_invalid_quvalues() {
480        let mut headers = rama_http_types::HeaderMap::new();
481        headers.append(
482            rama_http_types::header::ACCEPT_ENCODING,
483            rama_http_types::HeaderValue::from_static("gzip;q=-0.1"),
484        );
485        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
486        assert_eq!(Encoding::Identity, encoding);
487
488        let mut headers = rama_http_types::HeaderMap::new();
489        headers.append(
490            rama_http_types::header::ACCEPT_ENCODING,
491            rama_http_types::HeaderValue::from_static("gzip;q=00.5"),
492        );
493        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
494        assert_eq!(Encoding::Identity, encoding);
495
496        let mut headers = rama_http_types::HeaderMap::new();
497        headers.append(
498            rama_http_types::header::ACCEPT_ENCODING,
499            rama_http_types::HeaderValue::from_static("gzip;q=0.5000"),
500        );
501        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
502        assert_eq!(Encoding::Identity, encoding);
503
504        let mut headers = rama_http_types::HeaderMap::new();
505        headers.append(
506            rama_http_types::header::ACCEPT_ENCODING,
507            rama_http_types::HeaderValue::from_static("gzip;q=.5"),
508        );
509        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
510        assert_eq!(Encoding::Identity, encoding);
511
512        let mut headers = rama_http_types::HeaderMap::new();
513        headers.append(
514            rama_http_types::header::ACCEPT_ENCODING,
515            rama_http_types::HeaderValue::from_static("gzip;q=1.01"),
516        );
517        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
518        assert_eq!(Encoding::Identity, encoding);
519
520        let mut headers = rama_http_types::HeaderMap::new();
521        headers.append(
522            rama_http_types::header::ACCEPT_ENCODING,
523            rama_http_types::HeaderValue::from_static("gzip;q=1.001"),
524        );
525        let encoding = Encoding::from_accept_encoding_headers(&headers, SupportedEncodingsAll);
526        assert_eq!(Encoding::Identity, encoding);
527    }
528}