tower_http/
content_encoding.rs

1pub(crate) trait SupportedEncodings: Copy {
2    fn gzip(&self) -> bool;
3    fn deflate(&self) -> bool;
4    fn br(&self) -> bool;
5    fn zstd(&self) -> bool;
6}
7
8// This enum's variants are ordered from least to most preferred.
9#[derive(Copy, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
10pub(crate) enum Encoding {
11    #[allow(dead_code)]
12    Identity,
13    #[cfg(any(feature = "fs", feature = "compression-deflate"))]
14    Deflate,
15    #[cfg(any(feature = "fs", feature = "compression-gzip"))]
16    Gzip,
17    #[cfg(any(feature = "fs", feature = "compression-br"))]
18    Brotli,
19    #[cfg(any(feature = "fs", feature = "compression-zstd"))]
20    Zstd,
21}
22
23impl Encoding {
24    #[allow(dead_code)]
25    fn to_str(self) -> &'static str {
26        match self {
27            #[cfg(any(feature = "fs", feature = "compression-gzip"))]
28            Encoding::Gzip => "gzip",
29            #[cfg(any(feature = "fs", feature = "compression-deflate"))]
30            Encoding::Deflate => "deflate",
31            #[cfg(any(feature = "fs", feature = "compression-br"))]
32            Encoding::Brotli => "br",
33            #[cfg(any(feature = "fs", feature = "compression-zstd"))]
34            Encoding::Zstd => "zstd",
35            Encoding::Identity => "identity",
36        }
37    }
38
39    #[cfg(feature = "fs")]
40    pub(crate) fn to_file_extension(self) -> Option<&'static std::ffi::OsStr> {
41        match self {
42            Encoding::Gzip => Some(std::ffi::OsStr::new(".gz")),
43            Encoding::Deflate => Some(std::ffi::OsStr::new(".zz")),
44            Encoding::Brotli => Some(std::ffi::OsStr::new(".br")),
45            Encoding::Zstd => Some(std::ffi::OsStr::new(".zst")),
46            Encoding::Identity => None,
47        }
48    }
49
50    #[allow(dead_code)]
51    pub(crate) fn into_header_value(self) -> http::HeaderValue {
52        http::HeaderValue::from_static(self.to_str())
53    }
54
55    #[cfg(any(
56        feature = "compression-gzip",
57        feature = "compression-br",
58        feature = "compression-deflate",
59        feature = "compression-zstd",
60        feature = "fs",
61    ))]
62    fn parse(s: &str, _supported_encoding: impl SupportedEncodings) -> Option<Encoding> {
63        #[cfg(any(feature = "fs", feature = "compression-gzip"))]
64        if (s.eq_ignore_ascii_case("gzip") || s.eq_ignore_ascii_case("x-gzip"))
65            && _supported_encoding.gzip()
66        {
67            return Some(Encoding::Gzip);
68        }
69
70        #[cfg(any(feature = "fs", feature = "compression-deflate"))]
71        if s.eq_ignore_ascii_case("deflate") && _supported_encoding.deflate() {
72            return Some(Encoding::Deflate);
73        }
74
75        #[cfg(any(feature = "fs", feature = "compression-br"))]
76        if s.eq_ignore_ascii_case("br") && _supported_encoding.br() {
77            return Some(Encoding::Brotli);
78        }
79
80        #[cfg(any(feature = "fs", feature = "compression-zstd"))]
81        if s.eq_ignore_ascii_case("zstd") && _supported_encoding.zstd() {
82            return Some(Encoding::Zstd);
83        }
84
85        if s.eq_ignore_ascii_case("identity") {
86            return Some(Encoding::Identity);
87        }
88
89        None
90    }
91
92    #[cfg(any(
93        feature = "compression-gzip",
94        feature = "compression-br",
95        feature = "compression-zstd",
96        feature = "compression-deflate",
97    ))]
98    // based on https://github.com/http-rs/accept-encoding
99    pub(crate) fn from_headers(
100        headers: &http::HeaderMap,
101        supported_encoding: impl SupportedEncodings,
102    ) -> Self {
103        Encoding::preferred_encoding(encodings(headers, supported_encoding))
104            .unwrap_or(Encoding::Identity)
105    }
106
107    #[cfg(any(
108        feature = "compression-gzip",
109        feature = "compression-br",
110        feature = "compression-zstd",
111        feature = "compression-deflate",
112        feature = "fs",
113    ))]
114    pub(crate) fn preferred_encoding(
115        accepted_encodings: impl Iterator<Item = (Encoding, QValue)>,
116    ) -> Option<Self> {
117        accepted_encodings
118            .filter(|(_, qvalue)| qvalue.0 > 0)
119            .max_by_key(|&(encoding, qvalue)| (qvalue, encoding))
120            .map(|(encoding, _)| encoding)
121    }
122}
123
124// Allowed q-values are numbers between 0 and 1 with at most 3 digits in the fractional part. They
125// are presented here as an unsigned integer between 0 and 1000.
126#[cfg(any(
127    feature = "compression-gzip",
128    feature = "compression-br",
129    feature = "compression-zstd",
130    feature = "compression-deflate",
131    feature = "fs",
132))]
133#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
134pub(crate) struct QValue(u16);
135
136#[cfg(any(
137    feature = "compression-gzip",
138    feature = "compression-br",
139    feature = "compression-zstd",
140    feature = "compression-deflate",
141    feature = "fs",
142))]
143impl QValue {
144    #[inline]
145    pub(crate) fn one() -> Self {
146        Self(1000)
147    }
148
149    // Parse a q-value as specified in RFC 7231 section 5.3.1.
150    fn parse(s: &str) -> Option<Self> {
151        let mut c = s.chars();
152        // Parse "q=" (case-insensitively).
153        match c.next() {
154            Some('q' | 'Q') => (),
155            _ => return None,
156        };
157        match c.next() {
158            Some('=') => (),
159            _ => return None,
160        };
161
162        // Parse leading digit. Since valid q-values are between 0.000 and 1.000, only "0" and "1"
163        // are allowed.
164        let mut value = match c.next() {
165            Some('0') => 0,
166            Some('1') => 1000,
167            _ => return None,
168        };
169
170        // Parse optional decimal point.
171        match c.next() {
172            Some('.') => (),
173            None => return Some(Self(value)),
174            _ => return None,
175        };
176
177        // Parse optional fractional digits. The value of each digit is multiplied by `factor`.
178        // Since the q-value is represented as an integer between 0 and 1000, `factor` is `100` for
179        // the first digit, `10` for the next, and `1` for the digit after that.
180        let mut factor = 100;
181        loop {
182            match c.next() {
183                Some(n @ '0'..='9') => {
184                    // If `factor` is less than `1`, three digits have already been parsed. A
185                    // q-value having more than 3 fractional digits is invalid.
186                    if factor < 1 {
187                        return None;
188                    }
189                    // Add the digit's value multiplied by `factor` to `value`.
190                    value += factor * (n as u16 - '0' as u16);
191                }
192                None => {
193                    // No more characters to parse. Check that the value representing the q-value is
194                    // in the valid range.
195                    return if value <= 1000 {
196                        Some(Self(value))
197                    } else {
198                        None
199                    };
200                }
201                _ => return None,
202            };
203            factor /= 10;
204        }
205    }
206}
207
208#[cfg(any(
209    feature = "compression-gzip",
210    feature = "compression-br",
211    feature = "compression-zstd",
212    feature = "compression-deflate",
213    feature = "fs",
214))]
215// based on https://github.com/http-rs/accept-encoding
216pub(crate) fn encodings<'a>(
217    headers: &'a http::HeaderMap,
218    supported_encoding: impl SupportedEncodings + 'a,
219) -> impl Iterator<Item = (Encoding, QValue)> + 'a {
220    headers
221        .get_all(http::header::ACCEPT_ENCODING)
222        .iter()
223        .filter_map(|hval| hval.to_str().ok())
224        .flat_map(|s| s.split(','))
225        .filter_map(move |v| {
226            let mut v = v.splitn(2, ';');
227
228            let encoding = match Encoding::parse(v.next().unwrap().trim(), supported_encoding) {
229                Some(encoding) => encoding,
230                None => return None, // ignore unknown encodings
231            };
232
233            let qval = if let Some(qval) = v.next() {
234                QValue::parse(qval.trim())?
235            } else {
236                QValue::one()
237            };
238
239            Some((encoding, qval))
240        })
241}
242
243#[cfg(all(
244    test,
245    feature = "compression-gzip",
246    feature = "compression-deflate",
247    feature = "compression-br",
248    feature = "compression-zstd",
249))]
250mod tests {
251    use super::*;
252
253    #[derive(Copy, Clone, Default)]
254    struct SupportedEncodingsAll;
255
256    impl SupportedEncodings for SupportedEncodingsAll {
257        fn gzip(&self) -> bool {
258            true
259        }
260
261        fn deflate(&self) -> bool {
262            true
263        }
264
265        fn br(&self) -> bool {
266            true
267        }
268
269        fn zstd(&self) -> bool {
270            true
271        }
272    }
273
274    #[test]
275    fn no_accept_encoding_header() {
276        let encoding = Encoding::from_headers(&http::HeaderMap::new(), SupportedEncodingsAll);
277        assert_eq!(Encoding::Identity, encoding);
278    }
279
280    #[test]
281    fn accept_encoding_header_single_encoding() {
282        let mut headers = http::HeaderMap::new();
283        headers.append(
284            http::header::ACCEPT_ENCODING,
285            http::HeaderValue::from_static("gzip"),
286        );
287        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
288        assert_eq!(Encoding::Gzip, encoding);
289    }
290
291    #[test]
292    fn accept_encoding_header_two_encodings() {
293        let mut headers = http::HeaderMap::new();
294        headers.append(
295            http::header::ACCEPT_ENCODING,
296            http::HeaderValue::from_static("gzip,br"),
297        );
298        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
299        assert_eq!(Encoding::Brotli, encoding);
300    }
301
302    #[test]
303    fn accept_encoding_header_gzip_x_gzip() {
304        let mut headers = http::HeaderMap::new();
305        headers.append(
306            http::header::ACCEPT_ENCODING,
307            http::HeaderValue::from_static("gzip,x-gzip"),
308        );
309        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
310        assert_eq!(Encoding::Gzip, encoding);
311    }
312
313    #[test]
314    fn accept_encoding_header_x_gzip_deflate() {
315        let mut headers = http::HeaderMap::new();
316        headers.append(
317            http::header::ACCEPT_ENCODING,
318            http::HeaderValue::from_static("deflate,x-gzip"),
319        );
320        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
321        assert_eq!(Encoding::Gzip, encoding);
322    }
323
324    #[test]
325    fn accept_encoding_header_three_encodings() {
326        let mut headers = http::HeaderMap::new();
327        headers.append(
328            http::header::ACCEPT_ENCODING,
329            http::HeaderValue::from_static("gzip,deflate,br"),
330        );
331        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
332        assert_eq!(Encoding::Brotli, encoding);
333    }
334
335    #[test]
336    fn accept_encoding_header_two_encodings_with_one_qvalue() {
337        let mut headers = http::HeaderMap::new();
338        headers.append(
339            http::header::ACCEPT_ENCODING,
340            http::HeaderValue::from_static("gzip;q=0.5,br"),
341        );
342        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
343        assert_eq!(Encoding::Brotli, encoding);
344    }
345
346    #[test]
347    fn accept_encoding_header_three_encodings_with_one_qvalue() {
348        let mut headers = http::HeaderMap::new();
349        headers.append(
350            http::header::ACCEPT_ENCODING,
351            http::HeaderValue::from_static("gzip;q=0.5,deflate,br"),
352        );
353        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
354        assert_eq!(Encoding::Brotli, encoding);
355    }
356
357    #[test]
358    fn two_accept_encoding_headers_with_one_qvalue() {
359        let mut headers = http::HeaderMap::new();
360        headers.append(
361            http::header::ACCEPT_ENCODING,
362            http::HeaderValue::from_static("gzip;q=0.5"),
363        );
364        headers.append(
365            http::header::ACCEPT_ENCODING,
366            http::HeaderValue::from_static("br"),
367        );
368        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
369        assert_eq!(Encoding::Brotli, encoding);
370    }
371
372    #[test]
373    fn two_accept_encoding_headers_three_encodings_with_one_qvalue() {
374        let mut headers = http::HeaderMap::new();
375        headers.append(
376            http::header::ACCEPT_ENCODING,
377            http::HeaderValue::from_static("gzip;q=0.5,deflate"),
378        );
379        headers.append(
380            http::header::ACCEPT_ENCODING,
381            http::HeaderValue::from_static("br"),
382        );
383        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
384        assert_eq!(Encoding::Brotli, encoding);
385    }
386
387    #[test]
388    fn three_accept_encoding_headers_with_one_qvalue() {
389        let mut headers = http::HeaderMap::new();
390        headers.append(
391            http::header::ACCEPT_ENCODING,
392            http::HeaderValue::from_static("gzip;q=0.5"),
393        );
394        headers.append(
395            http::header::ACCEPT_ENCODING,
396            http::HeaderValue::from_static("deflate"),
397        );
398        headers.append(
399            http::header::ACCEPT_ENCODING,
400            http::HeaderValue::from_static("br"),
401        );
402        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
403        assert_eq!(Encoding::Brotli, encoding);
404    }
405
406    #[test]
407    fn accept_encoding_header_two_encodings_with_two_qvalues() {
408        let mut headers = http::HeaderMap::new();
409        headers.append(
410            http::header::ACCEPT_ENCODING,
411            http::HeaderValue::from_static("gzip;q=0.5,br;q=0.8"),
412        );
413        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
414        assert_eq!(Encoding::Brotli, encoding);
415
416        let mut headers = http::HeaderMap::new();
417        headers.append(
418            http::header::ACCEPT_ENCODING,
419            http::HeaderValue::from_static("gzip;q=0.8,br;q=0.5"),
420        );
421        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
422        assert_eq!(Encoding::Gzip, encoding);
423
424        let mut headers = http::HeaderMap::new();
425        headers.append(
426            http::header::ACCEPT_ENCODING,
427            http::HeaderValue::from_static("gzip;q=0.995,br;q=0.999"),
428        );
429        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
430        assert_eq!(Encoding::Brotli, encoding);
431    }
432
433    #[test]
434    fn accept_encoding_header_three_encodings_with_three_qvalues() {
435        let mut headers = http::HeaderMap::new();
436        headers.append(
437            http::header::ACCEPT_ENCODING,
438            http::HeaderValue::from_static("gzip;q=0.5,deflate;q=0.6,br;q=0.8"),
439        );
440        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
441        assert_eq!(Encoding::Brotli, encoding);
442
443        let mut headers = http::HeaderMap::new();
444        headers.append(
445            http::header::ACCEPT_ENCODING,
446            http::HeaderValue::from_static("gzip;q=0.8,deflate;q=0.6,br;q=0.5"),
447        );
448        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
449        assert_eq!(Encoding::Gzip, encoding);
450
451        let mut headers = http::HeaderMap::new();
452        headers.append(
453            http::header::ACCEPT_ENCODING,
454            http::HeaderValue::from_static("gzip;q=0.6,deflate;q=0.8,br;q=0.5"),
455        );
456        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
457        assert_eq!(Encoding::Deflate, encoding);
458
459        let mut headers = http::HeaderMap::new();
460        headers.append(
461            http::header::ACCEPT_ENCODING,
462            http::HeaderValue::from_static("gzip;q=0.995,deflate;q=0.997,br;q=0.999"),
463        );
464        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
465        assert_eq!(Encoding::Brotli, encoding);
466    }
467
468    #[test]
469    fn accept_encoding_header_invalid_encdoing() {
470        let mut headers = http::HeaderMap::new();
471        headers.append(
472            http::header::ACCEPT_ENCODING,
473            http::HeaderValue::from_static("invalid,gzip"),
474        );
475        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
476        assert_eq!(Encoding::Gzip, encoding);
477    }
478
479    #[test]
480    fn accept_encoding_header_with_qvalue_zero() {
481        let mut headers = http::HeaderMap::new();
482        headers.append(
483            http::header::ACCEPT_ENCODING,
484            http::HeaderValue::from_static("gzip;q=0"),
485        );
486        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
487        assert_eq!(Encoding::Identity, encoding);
488
489        let mut headers = http::HeaderMap::new();
490        headers.append(
491            http::header::ACCEPT_ENCODING,
492            http::HeaderValue::from_static("gzip;q=0."),
493        );
494        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
495        assert_eq!(Encoding::Identity, encoding);
496
497        let mut headers = http::HeaderMap::new();
498        headers.append(
499            http::header::ACCEPT_ENCODING,
500            http::HeaderValue::from_static("gzip;q=0,br;q=0.5"),
501        );
502        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
503        assert_eq!(Encoding::Brotli, encoding);
504    }
505
506    #[test]
507    fn accept_encoding_header_with_uppercase_letters() {
508        let mut headers = http::HeaderMap::new();
509        headers.append(
510            http::header::ACCEPT_ENCODING,
511            http::HeaderValue::from_static("gZiP"),
512        );
513        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
514        assert_eq!(Encoding::Gzip, encoding);
515
516        let mut headers = http::HeaderMap::new();
517        headers.append(
518            http::header::ACCEPT_ENCODING,
519            http::HeaderValue::from_static("gzip;q=0.5,br;Q=0.8"),
520        );
521        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
522        assert_eq!(Encoding::Brotli, encoding);
523    }
524
525    #[test]
526    fn accept_encoding_header_with_allowed_spaces() {
527        let mut headers = http::HeaderMap::new();
528        headers.append(
529            http::header::ACCEPT_ENCODING,
530            http::HeaderValue::from_static(" gzip\t; q=0.5 ,\tbr ;\tq=0.8\t"),
531        );
532        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
533        assert_eq!(Encoding::Brotli, encoding);
534    }
535
536    #[test]
537    fn accept_encoding_header_with_invalid_spaces() {
538        let mut headers = http::HeaderMap::new();
539        headers.append(
540            http::header::ACCEPT_ENCODING,
541            http::HeaderValue::from_static("gzip;q =0.5"),
542        );
543        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
544        assert_eq!(Encoding::Identity, encoding);
545
546        let mut headers = http::HeaderMap::new();
547        headers.append(
548            http::header::ACCEPT_ENCODING,
549            http::HeaderValue::from_static("gzip;q= 0.5"),
550        );
551        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
552        assert_eq!(Encoding::Identity, encoding);
553    }
554
555    #[test]
556    fn accept_encoding_header_with_invalid_quvalues() {
557        let mut headers = http::HeaderMap::new();
558        headers.append(
559            http::header::ACCEPT_ENCODING,
560            http::HeaderValue::from_static("gzip;q=-0.1"),
561        );
562        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
563        assert_eq!(Encoding::Identity, encoding);
564
565        let mut headers = http::HeaderMap::new();
566        headers.append(
567            http::header::ACCEPT_ENCODING,
568            http::HeaderValue::from_static("gzip;q=00.5"),
569        );
570        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
571        assert_eq!(Encoding::Identity, encoding);
572
573        let mut headers = http::HeaderMap::new();
574        headers.append(
575            http::header::ACCEPT_ENCODING,
576            http::HeaderValue::from_static("gzip;q=0.5000"),
577        );
578        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
579        assert_eq!(Encoding::Identity, encoding);
580
581        let mut headers = http::HeaderMap::new();
582        headers.append(
583            http::header::ACCEPT_ENCODING,
584            http::HeaderValue::from_static("gzip;q=.5"),
585        );
586        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
587        assert_eq!(Encoding::Identity, encoding);
588
589        let mut headers = http::HeaderMap::new();
590        headers.append(
591            http::header::ACCEPT_ENCODING,
592            http::HeaderValue::from_static("gzip;q=1.01"),
593        );
594        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
595        assert_eq!(Encoding::Identity, encoding);
596
597        let mut headers = http::HeaderMap::new();
598        headers.append(
599            http::header::ACCEPT_ENCODING,
600            http::HeaderValue::from_static("gzip;q=1.001"),
601        );
602        let encoding = Encoding::from_headers(&headers, SupportedEncodingsAll);
603        assert_eq!(Encoding::Identity, encoding);
604    }
605}