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#[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 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#[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 fn parse(s: &str) -> Option<Self> {
151 let mut c = s.chars();
152 match c.next() {
154 Some('q' | 'Q') => (),
155 _ => return None,
156 };
157 match c.next() {
158 Some('=') => (),
159 _ => return None,
160 };
161
162 let mut value = match c.next() {
165 Some('0') => 0,
166 Some('1') => 1000,
167 _ => return None,
168 };
169
170 match c.next() {
172 Some('.') => (),
173 None => return Some(Self(value)),
174 _ => return None,
175 };
176
177 let mut factor = 100;
181 loop {
182 match c.next() {
183 Some(n @ '0'..='9') => {
184 if factor < 1 {
187 return None;
188 }
189 value += factor * (n as u16 - '0' as u16);
191 }
192 None => {
193 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))]
215pub(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, };
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}