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>>)*
56
57 test_parse_and_format {
58 common_header_test!(no_headers, [b""; 0], Some(AcceptEncoding(vec![])));
59 common_header_test!(empty_header, [b""; 1], Some(AcceptEncoding(vec![])));
60
61 common_header_test!(
62 order_of_appearance,
63 [b"br, gzip"],
64 Some(AcceptEncoding(vec![
65 QualityItem::max(Preference::Specific(Encoding::brotli())),
66 QualityItem::max(Preference::Specific(Encoding::gzip())),
67 ]))
68 );
69
70 common_header_test!(any, [b"*"], Some(AcceptEncoding(vec![
71 QualityItem::max(Preference::Any),
72 ])));
73
74 common_header_test!(implicit_quality, [b"gzip, identity; q=0.5, *;q=0"]);
76
77 common_header_test!(implicit_quality_out_of_order, [b"compress;q=0.5, gzip"]);
79
80 common_header_test!(
81 only_gzip_no_identity,
82 [b"gzip, *; q=0"],
83 Some(AcceptEncoding(vec![
84 QualityItem::max(Preference::Specific(Encoding::gzip())),
85 QualityItem::zero(Preference::Any),
86 ]))
87 );
88 }
89}
90
91impl AcceptEncoding {
92 pub fn negotiate<'a>(&self, supported: impl Iterator<Item = &'a Encoding>) -> Option<Encoding> {
103 let supported_set = supported.collect::<HashSet<_>>();
107
108 if supported_set.is_empty() {
109 return None;
110 }
111
112 if self.0.is_empty() {
113 return Some(Encoding::identity());
115 }
116
117 let acceptable_items = self.ranked_items().collect::<Vec<_>>();
122
123 let identity_acceptable = is_identity_acceptable(&acceptable_items);
124 let identity_supported = supported_set.contains(&Encoding::identity());
125
126 if identity_acceptable && identity_supported && supported_set.len() == 1 {
127 return Some(Encoding::identity());
128 }
129
130 let matched = acceptable_items
137 .into_iter()
138 .filter(|q| q.quality > Quality::ZERO)
139 .find(|q| {
141 let enc = &q.item;
142 matches!(enc, Preference::Specific(enc) if supported_set.contains(enc))
143 })
144 .map(|q| q.item);
145
146 match matched {
147 Some(Preference::Specific(enc)) => Some(enc),
148
149 _ if identity_acceptable => Some(Encoding::identity()),
150
151 _ => None,
152 }
153 }
154
155 pub fn preference(&self) -> Option<Preference<Encoding>> {
168 if self.0.is_empty() {
170 return Some(Preference::Any);
171 }
172
173 let mut max_item = None;
174 let mut max_pref = Quality::ZERO;
175 let mut max_rank = 0;
176
177 for pref in &self.0 {
181 let rank = encoding_rank(pref);
185
186 if (pref.quality, rank) > (max_pref, max_rank) {
187 max_pref = pref.quality;
188 max_item = Some(pref.item.clone());
189 max_rank = rank;
190 }
191 }
192
193 max_item.or_else(|| {
195 match self.0.iter().find(|pref| {
198 matches!(
199 pref.item,
200 Preference::Any
201 | Preference::Specific(Encoding::Known(ContentEncoding::Identity))
202 )
203 }) {
204 Some(_) => None,
206
207 None => Some(Preference::Specific(Encoding::identity())),
209 }
210 })
211 }
212
213 pub fn ranked(&self) -> Vec<Preference<Encoding>> {
220 self.ranked_items().map(|q| q.item).collect()
221 }
222
223 fn ranked_items(&self) -> impl Iterator<Item = QualityItem<Preference<Encoding>>> {
224 if self.0.is_empty() {
225 return Vec::new().into_iter();
226 }
227
228 let mut types = self.0.clone();
229
230 types.sort_by(|a, b| {
232 b.quality
235 .cmp(&a.quality)
236 .then(encoding_rank(b).cmp(&encoding_rank(a)))
237 });
238
239 types.into_iter()
240 }
241}
242
243fn encoding_rank(qv: &QualityItem<Preference<Encoding>>) -> u8 {
245 if qv.quality == Quality::ZERO {
248 return 0;
249 }
250
251 match qv.item {
252 Preference::Specific(Encoding::Known(ContentEncoding::Brotli)) => 5,
253 Preference::Specific(Encoding::Known(ContentEncoding::Zstd)) => 4,
254 Preference::Specific(Encoding::Known(ContentEncoding::Gzip)) => 3,
255 Preference::Specific(Encoding::Known(ContentEncoding::Deflate)) => 2,
256 Preference::Any => 0,
257 Preference::Specific(Encoding::Known(ContentEncoding::Identity)) => 0,
258 Preference::Specific(Encoding::Known(_)) => 1,
259 Preference::Specific(Encoding::Unknown(_)) => 1,
260 }
261}
262
263fn is_identity_acceptable(items: &'_ [QualityItem<Preference<Encoding>>]) -> bool {
267 if items.is_empty() {
268 return true;
269 }
270
271 for q in items {
274 match (q.quality, &q.item) {
275 (q, Preference::Specific(Encoding::Known(ContentEncoding::Identity))) => {
277 return q > Quality::ZERO
278 }
279
280 (q, Preference::Any) => return q > Quality::ZERO,
282
283 _ => {}
284 }
285 }
286
287 true
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use crate::http::header::*;
295
296 macro_rules! accept_encoding {
297 () => { AcceptEncoding(vec![]) };
298 ($($q:expr),+ $(,)?) => { AcceptEncoding(vec![$($q.parse().unwrap()),+]) };
299 }
300
301 fn enc(enc: &str) -> Preference<Encoding> {
303 enc.parse().unwrap()
304 }
305
306 #[test]
307 fn detect_identity_acceptable() {
308 macro_rules! accept_encoding_ranked {
309 () => { accept_encoding!().ranked_items().collect::<Vec<_>>() };
310 ($($q:expr),+ $(,)?) => { accept_encoding!($($q),+).ranked_items().collect::<Vec<_>>() };
311 }
312
313 let test = accept_encoding_ranked!();
314 assert!(is_identity_acceptable(&test));
315 let test = accept_encoding_ranked!("gzip");
316 assert!(is_identity_acceptable(&test));
317 let test = accept_encoding_ranked!("gzip", "br");
318 assert!(is_identity_acceptable(&test));
319 let test = accept_encoding_ranked!("gzip", "*;q=0.1");
320 assert!(is_identity_acceptable(&test));
321 let test = accept_encoding_ranked!("gzip", "identity;q=0.1");
322 assert!(is_identity_acceptable(&test));
323 let test = accept_encoding_ranked!("gzip", "identity;q=0.1", "*;q=0");
324 assert!(is_identity_acceptable(&test));
325 let test = accept_encoding_ranked!("gzip", "*;q=0", "identity;q=0.1");
326 assert!(is_identity_acceptable(&test));
327
328 let test = accept_encoding_ranked!("gzip", "*;q=0");
329 assert!(!is_identity_acceptable(&test));
330 let test = accept_encoding_ranked!("gzip", "identity;q=0");
331 assert!(!is_identity_acceptable(&test));
332 let test = accept_encoding_ranked!("gzip", "identity;q=0", "*;q=0");
333 assert!(!is_identity_acceptable(&test));
334 let test = accept_encoding_ranked!("gzip", "*;q=0", "identity;q=0");
335 assert!(!is_identity_acceptable(&test));
336 }
337
338 #[test]
339 fn encoding_negotiation() {
340 let test = accept_encoding!();
342 assert_eq!(test.negotiate([].iter()), None);
343
344 let test = accept_encoding!();
345 assert_eq!(
346 test.negotiate([Encoding::identity()].iter()),
347 Some(Encoding::identity()),
348 );
349
350 let test = accept_encoding!("identity;q=0");
351 assert_eq!(test.negotiate([Encoding::identity()].iter()), None);
352
353 let test = accept_encoding!("*;q=0");
354 assert_eq!(test.negotiate([Encoding::identity()].iter()), None);
355
356 let test = accept_encoding!();
357 assert_eq!(
358 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
359 Some(Encoding::identity()),
360 );
361
362 let test = accept_encoding!("gzip");
363 assert_eq!(
364 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
365 Some(Encoding::gzip()),
366 );
367 assert_eq!(
368 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
369 Some(Encoding::identity()),
370 );
371 assert_eq!(
372 test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()),
373 Some(Encoding::gzip()),
374 );
375
376 let test = accept_encoding!("gzip", "identity;q=0");
377 assert_eq!(
378 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
379 Some(Encoding::gzip()),
380 );
381 assert_eq!(
382 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
383 None
384 );
385
386 let test = accept_encoding!("gzip", "*;q=0");
387 assert_eq!(
388 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
389 Some(Encoding::gzip()),
390 );
391 assert_eq!(
392 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
393 None
394 );
395
396 let test = accept_encoding!("gzip", "deflate", "br");
397 assert_eq!(
398 test.negotiate([Encoding::gzip(), Encoding::identity()].iter()),
399 Some(Encoding::gzip()),
400 );
401 assert_eq!(
402 test.negotiate([Encoding::brotli(), Encoding::identity()].iter()),
403 Some(Encoding::brotli())
404 );
405 assert_eq!(
406 test.negotiate([Encoding::deflate(), Encoding::identity()].iter()),
407 Some(Encoding::deflate())
408 );
409 assert_eq!(
410 test.negotiate([Encoding::gzip(), Encoding::deflate(), Encoding::identity()].iter()),
411 Some(Encoding::gzip())
412 );
413 assert_eq!(
414 test.negotiate([Encoding::gzip(), Encoding::brotli(), Encoding::identity()].iter()),
415 Some(Encoding::brotli())
416 );
417 assert_eq!(
418 test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()),
419 Some(Encoding::brotli())
420 );
421 }
422
423 #[test]
424 fn ranking_precedence() {
425 let test = accept_encoding!();
426 assert!(test.ranked().is_empty());
427
428 let test = accept_encoding!("gzip");
429 assert_eq!(test.ranked(), vec![enc("gzip")]);
430
431 let test = accept_encoding!("gzip;q=0.900", "*;q=0.700", "br;q=1.0");
432 assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
433
434 let test = accept_encoding!("br", "gzip", "*");
435 assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
436
437 let test = accept_encoding!("gzip", "br", "*");
438 assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
439 }
440
441 #[test]
442 fn preference_selection() {
443 assert_eq!(accept_encoding!().preference(), Some(Preference::Any));
444
445 assert_eq!(accept_encoding!("identity;q=0").preference(), None);
446 assert_eq!(accept_encoding!("*;q=0").preference(), None);
447 assert_eq!(accept_encoding!("compress;q=0", "*;q=0").preference(), None);
448 assert_eq!(accept_encoding!("identity;q=0", "*;q=0").preference(), None);
449
450 let test = accept_encoding!("*;q=0.5");
451 assert_eq!(test.preference().unwrap(), enc("*"));
452
453 let test = accept_encoding!("br;q=0");
454 assert_eq!(test.preference().unwrap(), enc("identity"));
455
456 let test = accept_encoding!("br;q=0.900", "gzip;q=1.0", "*;q=0.500");
457 assert_eq!(test.preference().unwrap(), enc("gzip"));
458
459 let test = accept_encoding!("br", "gzip", "*");
460 assert_eq!(test.preference().unwrap(), enc("br"));
461
462 let test = accept_encoding!("gzip", "br", "*");
463 assert_eq!(test.preference().unwrap(), enc("br"));
464 }
465}