1use 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)]
37pub 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
141pub 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, };
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}