1use std::collections::HashSet;
2
3use super::{common_header, ContentEncoding, Encoding, Preference, Quality, QualityItem};
4use crate::http::header;
5
6common_header! {
7 (AcceptEncoding, header::ACCEPT_ENCODING) => (QualityItem<Preference<Encoding>>)*
51
52 test_parse_and_format {
53 common_header_test!(no_headers, [b""; 0], Some(AcceptEncoding(vec![])));
54 common_header_test!(empty_header, [b""; 1], Some(AcceptEncoding(vec![])));
55
56 common_header_test!(
57 order_of_appearance,
58 [b"br, gzip"],
59 Some(AcceptEncoding(vec![
60 QualityItem::max(Preference::Specific(Encoding::brotli())),
61 QualityItem::max(Preference::Specific(Encoding::gzip())),
62 ]))
63 );
64
65 common_header_test!(any, [b"*"], Some(AcceptEncoding(vec![
66 QualityItem::max(Preference::Any),
67 ])));
68
69 common_header_test!(implicit_quality, [b"gzip, identity; q=0.5, *;q=0"]);
71
72 common_header_test!(implicit_quality_out_of_order, [b"compress;q=0.5, gzip"]);
74
75 common_header_test!(
76 only_gzip_no_identity,
77 [b"gzip, *; q=0"],
78 Some(AcceptEncoding(vec![
79 QualityItem::max(Preference::Specific(Encoding::gzip())),
80 QualityItem::zero(Preference::Any),
81 ]))
82 );
83 }
84}
85
86impl AcceptEncoding {
87 pub fn negotiate<'a>(&self, supported: impl Iterator<Item = &'a Encoding>) -> Option<Encoding> {
98 let supported_set = supported.collect::<HashSet<_>>();
102
103 if supported_set.is_empty() {
104 return None;
105 }
106
107 if self.0.is_empty() {
108 return Some(Encoding::identity());
110 }
111
112 let acceptable_items = self.ranked_items().collect::<Vec<_>>();
117
118 let identity_acceptable = is_identity_acceptable(&acceptable_items);
119 let identity_supported = supported_set.contains(&Encoding::identity());
120
121 if identity_acceptable && identity_supported && supported_set.len() == 1 {
122 return Some(Encoding::identity());
123 }
124
125 let matched = acceptable_items
132 .into_iter()
133 .filter(|q| q.quality > Quality::ZERO)
134 .find(|q| {
136 let enc = &q.item;
137 matches!(enc, Preference::Specific(enc) if supported_set.contains(enc))
138 })
139 .map(|q| q.item);
140
141 match matched {
142 Some(Preference::Specific(enc)) => Some(enc),
143
144 _ if identity_acceptable => Some(Encoding::identity()),
145
146 _ => None,
147 }
148 }
149
150 pub fn preference(&self) -> Option<Preference<Encoding>> {
163 if self.0.is_empty() {
165 return Some(Preference::Any);
166 }
167
168 let mut max_item = None;
169 let mut max_pref = Quality::ZERO;
170 let mut max_rank = 0;
171
172 for pref in &self.0 {
176 let rank = encoding_rank(pref);
180
181 if (pref.quality, rank) > (max_pref, max_rank) {
182 max_pref = pref.quality;
183 max_item = Some(pref.item.clone());
184 max_rank = rank;
185 }
186 }
187
188 max_item.or_else(|| {
190 match self.0.iter().find(|pref| {
193 matches!(
194 pref.item,
195 Preference::Any
196 | Preference::Specific(Encoding::Known(ContentEncoding::Identity))
197 )
198 }) {
199 Some(_) => None,
201
202 None => Some(Preference::Specific(Encoding::identity())),
204 }
205 })
206 }
207
208 pub fn ranked(&self) -> Vec<Preference<Encoding>> {
215 self.ranked_items().map(|q| q.item).collect()
216 }
217
218 fn ranked_items(&self) -> impl Iterator<Item = QualityItem<Preference<Encoding>>> {
219 if self.0.is_empty() {
220 return Vec::new().into_iter();
221 }
222
223 let mut types = self.0.clone();
224
225 types.sort_by(|a, b| {
227 b.quality
230 .cmp(&a.quality)
231 .then(encoding_rank(b).cmp(&encoding_rank(a)))
232 });
233
234 types.into_iter()
235 }
236}
237
238fn encoding_rank(qv: &QualityItem<Preference<Encoding>>) -> u8 {
240 if qv.quality == Quality::ZERO {
243 return 0;
244 }
245
246 match qv.item {
247 Preference::Specific(Encoding::Known(ContentEncoding::Brotli)) => 5,
248 Preference::Specific(Encoding::Known(ContentEncoding::Zstd)) => 4,
249 Preference::Specific(Encoding::Known(ContentEncoding::Gzip)) => 3,
250 Preference::Specific(Encoding::Known(ContentEncoding::Deflate)) => 2,
251 Preference::Any => 0,
252 Preference::Specific(Encoding::Known(ContentEncoding::Identity)) => 0,
253 Preference::Specific(Encoding::Known(_)) => 1,
254 Preference::Specific(Encoding::Unknown(_)) => 1,
255 }
256}
257
258fn is_identity_acceptable(items: &'_ [QualityItem<Preference<Encoding>>]) -> bool {
262 if items.is_empty() {
263 return true;
264 }
265
266 for q in items {
269 match (q.quality, &q.item) {
270 (q, Preference::Specific(Encoding::Known(ContentEncoding::Identity))) => {
272 return q > Quality::ZERO
273 }
274
275 (q, Preference::Any) => return q > Quality::ZERO,
277
278 _ => {}
279 }
280 }
281
282 true
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::http::header::*;
290
291 macro_rules! accept_encoding {
292 () => { AcceptEncoding(vec![]) };
293 ($($q:expr),+ $(,)?) => { AcceptEncoding(vec![$($q.parse().unwrap()),+]) };
294 }
295
296 fn enc(enc: &str) -> Preference<Encoding> {
298 enc.parse().unwrap()
299 }
300
301 #[test]
302 fn detect_identity_acceptable() {
303 macro_rules! accept_encoding_ranked {
304 () => { accept_encoding!().ranked_items().collect::<Vec<_>>() };
305 ($($q:expr),+ $(,)?) => { accept_encoding!($($q),+).ranked_items().collect::<Vec<_>>() };
306 }
307
308 let test = accept_encoding_ranked!();
309 assert!(is_identity_acceptable(&test));
310 let test = accept_encoding_ranked!("gzip");
311 assert!(is_identity_acceptable(&test));
312 let test = accept_encoding_ranked!("gzip", "br");
313 assert!(is_identity_acceptable(&test));
314 let test = accept_encoding_ranked!("gzip", "*;q=0.1");
315 assert!(is_identity_acceptable(&test));
316 let test = accept_encoding_ranked!("gzip", "identity;q=0.1");
317 assert!(is_identity_acceptable(&test));
318 let test = accept_encoding_ranked!("gzip", "identity;q=0.1", "*;q=0");
319 assert!(is_identity_acceptable(&test));
320 let test = accept_encoding_ranked!("gzip", "*;q=0", "identity;q=0.1");
321 assert!(is_identity_acceptable(&test));
322
323 let test = accept_encoding_ranked!("gzip", "*;q=0");
324 assert!(!is_identity_acceptable(&test));
325 let test = accept_encoding_ranked!("gzip", "identity;q=0");
326 assert!(!is_identity_acceptable(&test));
327 let test = accept_encoding_ranked!("gzip", "identity;q=0", "*;q=0");
328 assert!(!is_identity_acceptable(&test));
329 let test = accept_encoding_ranked!("gzip", "*;q=0", "identity;q=0");
330 assert!(!is_identity_acceptable(&test));
331 }
332
333 #[test]
334 fn encoding_negotiation() {
335 let test = accept_encoding!();
337 assert_eq!(test.negotiate([].iter()), None);
338
339 let test = accept_encoding!();
340 assert_eq!(
341 test.negotiate([Encoding::identity()].iter()),
342 Some(Encoding::identity()),
343 );
344
345 let test = accept_encoding!("identity;q=0");
346 assert_eq!(test.negotiate([Encoding::identity()].iter()), None);
347
348 let test = accept_encoding!("*;q=0");
349 assert_eq!(test.negotiate([Encoding::identity()].iter()), None);
350
351 let test = accept_encoding!();
352 assert_eq!(
353 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
354 Some(Encoding::identity()),
355 );
356
357 let test = accept_encoding!("gzip");
358 assert_eq!(
359 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
360 Some(Encoding::gzip()),
361 );
362 assert_eq!(
363 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
364 Some(Encoding::identity()),
365 );
366 assert_eq!(
367 test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()),
368 Some(Encoding::gzip()),
369 );
370
371 let test = accept_encoding!("gzip", "identity;q=0");
372 assert_eq!(
373 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
374 Some(Encoding::gzip()),
375 );
376 assert_eq!(
377 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
378 None
379 );
380
381 let test = accept_encoding!("gzip", "*;q=0");
382 assert_eq!(
383 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
384 Some(Encoding::gzip()),
385 );
386 assert_eq!(
387 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
388 None
389 );
390
391 let test = accept_encoding!("gzip", "deflate", "br");
392 assert_eq!(
393 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
394 Some(Encoding::gzip()),
395 );
396 assert_eq!(
397 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
398 Some(Encoding::brotli())
399 );
400 assert_eq!(
401 test.negotiate([Encoding::deflate(), Encoding::identity()].iter()),
402 Some(Encoding::deflate())
403 );
404 assert_eq!(
405 test.negotiate([Encoding::gzip(), Encoding::deflate(), Encoding::identity()].iter()),
406 Some(Encoding::gzip())
407 );
408 assert_eq!(
409 test.negotiate([Encoding::gzip(), Encoding::brotli(), Encoding::identity()].iter()),
410 Some(Encoding::brotli())
411 );
412 assert_eq!(
413 test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()),
414 Some(Encoding::brotli())
415 );
416 }
417
418 #[test]
419 fn ranking_precedence() {
420 let test = accept_encoding!();
421 assert!(test.ranked().is_empty());
422
423 let test = accept_encoding!("gzip");
424 assert_eq!(test.ranked(), vec![enc("gzip")]);
425
426 let test = accept_encoding!("gzip;q=0.900", "*;q=0.700", "br;q=1.0");
427 assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
428
429 let test = accept_encoding!("br", "gzip", "*");
430 assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
431
432 let test = accept_encoding!("gzip", "br", "*");
433 assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
434 }
435
436 #[test]
437 fn preference_selection() {
438 assert_eq!(accept_encoding!().preference(), Some(Preference::Any));
439
440 assert_eq!(accept_encoding!("identity;q=0").preference(), None);
441 assert_eq!(accept_encoding!("*;q=0").preference(), None);
442 assert_eq!(accept_encoding!("compress;q=0", "*;q=0").preference(), None);
443 assert_eq!(accept_encoding!("identity;q=0", "*;q=0").preference(), None);
444
445 let test = accept_encoding!("*;q=0.5");
446 assert_eq!(test.preference().unwrap(), enc("*"));
447
448 let test = accept_encoding!("br;q=0");
449 assert_eq!(test.preference().unwrap(), enc("identity"));
450
451 let test = accept_encoding!("br;q=0.900", "gzip;q=1.0", "*;q=0.500");
452 assert_eq!(test.preference().unwrap(), enc("gzip"));
453
454 let test = accept_encoding!("br", "gzip", "*");
455 assert_eq!(test.preference().unwrap(), enc("br"));
456
457 let test = accept_encoding!("gzip", "br", "*");
458 assert_eq!(test.preference().unwrap(), enc("br"));
459 }
460}