http_types/content/
accept_encoding.rs

1//! Client header advertising available compression algorithms.
2
3use crate::content::{ContentEncoding, Encoding, EncodingProposal};
4use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, ACCEPT_ENCODING};
5use crate::utils::sort_by_weight;
6use crate::{Error, StatusCode};
7
8use std::fmt::{self, Debug, Write};
9use std::option;
10use std::slice;
11
12/// Client header advertising available compression algorithms.
13///
14/// # Specifications
15///
16/// - [RFC 7231, section 5.3.4: Accept-Encoding](https://tools.ietf.org/html/rfc7231#section-5.3.4)
17///
18/// # Examples
19///
20/// ```
21/// # fn main() -> http_types::Result<()> {
22/// #
23/// use http_types::content::{AcceptEncoding, ContentEncoding, Encoding, EncodingProposal};
24/// use http_types::Response;
25///
26/// let mut accept = AcceptEncoding::new();
27/// accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
28/// accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
29/// accept.push(EncodingProposal::new(Encoding::Identity, None)?);
30///
31/// let mut res = Response::new(200);
32/// let encoding = accept.negotiate(&[Encoding::Brotli, Encoding::Gzip])?;
33/// encoding.apply(&mut res);
34///
35/// assert_eq!(res["Content-Encoding"], "br");
36/// #
37/// # Ok(()) }
38/// ```
39pub struct AcceptEncoding {
40    wildcard: bool,
41    entries: Vec<EncodingProposal>,
42}
43
44impl AcceptEncoding {
45    /// Create a new instance of `AcceptEncoding`.
46    pub fn new() -> Self {
47        Self {
48            entries: vec![],
49            wildcard: false,
50        }
51    }
52
53    /// Create an instance of `AcceptEncoding` from a `Headers` instance.
54    pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
55        let mut entries = vec![];
56        let headers = match headers.as_ref().get(ACCEPT_ENCODING) {
57            Some(headers) => headers,
58            None => return Ok(None),
59        };
60
61        let mut wildcard = false;
62
63        for value in headers {
64            for part in value.as_str().trim().split(',') {
65                let part = part.trim();
66
67                // Handle empty strings, and wildcard directives.
68                if part.is_empty() {
69                    continue;
70                } else if part == "*" {
71                    wildcard = true;
72                    continue;
73                }
74
75                // Try and parse a directive from a str. If the directive is
76                // unkown we skip it.
77                if let Some(entry) = EncodingProposal::from_str(part)? {
78                    entries.push(entry);
79                }
80            }
81        }
82
83        Ok(Some(Self { entries, wildcard }))
84    }
85
86    /// Push a directive into the list of entries.
87    pub fn push(&mut self, prop: impl Into<EncodingProposal>) {
88        self.entries.push(prop.into());
89    }
90
91    /// Returns `true` if a wildcard directive was passed.
92    pub fn wildcard(&self) -> bool {
93        self.wildcard
94    }
95
96    /// Set the wildcard directive.
97    pub fn set_wildcard(&mut self, wildcard: bool) {
98        self.wildcard = wildcard
99    }
100
101    /// Sort the header directives by weight.
102    ///
103    /// Headers with a higher `q=` value will be returned first. If two
104    /// directives have the same weight, the directive that was declared later
105    /// will be returned first.
106    pub fn sort(&mut self) {
107        sort_by_weight(&mut self.entries);
108    }
109
110    /// Determine the most suitable `Content-Encoding` encoding.
111    ///
112    /// # Errors
113    ///
114    /// If no suitable encoding is found, an error with the status of `406` will be returned.
115    pub fn negotiate(&mut self, available: &[Encoding]) -> crate::Result<ContentEncoding> {
116        // Start by ordering the encodings.
117        self.sort();
118
119        // Try and find the first encoding that matches.
120        for encoding in &self.entries {
121            if available.contains(encoding) {
122                return Ok(encoding.into());
123            }
124        }
125
126        // If no encoding matches and wildcard is set, send whichever encoding we got.
127        if self.wildcard {
128            if let Some(encoding) = available.iter().next() {
129                return Ok(encoding.into());
130            }
131        }
132
133        let mut err = Error::new_adhoc("No suitable Content-Encoding found");
134        err.set_status(StatusCode::NotAcceptable);
135        Err(err)
136    }
137
138    /// Sets the `Accept-Encoding` header.
139    pub fn apply(&self, mut headers: impl AsMut<Headers>) {
140        headers.as_mut().insert(ACCEPT_ENCODING, self.value());
141    }
142
143    /// Get the `HeaderName`.
144    pub fn name(&self) -> HeaderName {
145        ACCEPT_ENCODING
146    }
147
148    /// Get the `HeaderValue`.
149    pub fn value(&self) -> HeaderValue {
150        let mut output = String::new();
151        for (n, directive) in self.entries.iter().enumerate() {
152            let directive: HeaderValue = (*directive).into();
153            match n {
154                0 => write!(output, "{}", directive).unwrap(),
155                _ => write!(output, ", {}", directive).unwrap(),
156            };
157        }
158
159        if self.wildcard {
160            match output.len() {
161                0 => write!(output, "*").unwrap(),
162                _ => write!(output, ", *").unwrap(),
163            }
164        }
165
166        // SAFETY: the internal string is validated to be ASCII.
167        unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
168    }
169
170    /// An iterator visiting all entries.
171    pub fn iter(&self) -> Iter<'_> {
172        Iter {
173            inner: self.entries.iter(),
174        }
175    }
176
177    /// An iterator visiting all entries.
178    pub fn iter_mut(&mut self) -> IterMut<'_> {
179        IterMut {
180            inner: self.entries.iter_mut(),
181        }
182    }
183}
184
185impl IntoIterator for AcceptEncoding {
186    type Item = EncodingProposal;
187    type IntoIter = IntoIter;
188
189    #[inline]
190    fn into_iter(self) -> Self::IntoIter {
191        IntoIter {
192            inner: self.entries.into_iter(),
193        }
194    }
195}
196
197impl<'a> IntoIterator for &'a AcceptEncoding {
198    type Item = &'a EncodingProposal;
199    type IntoIter = Iter<'a>;
200
201    #[inline]
202    fn into_iter(self) -> Self::IntoIter {
203        self.iter()
204    }
205}
206
207impl<'a> IntoIterator for &'a mut AcceptEncoding {
208    type Item = &'a mut EncodingProposal;
209    type IntoIter = IterMut<'a>;
210
211    #[inline]
212    fn into_iter(self) -> Self::IntoIter {
213        self.iter_mut()
214    }
215}
216
217/// A borrowing iterator over entries in `AcceptEncoding`.
218#[derive(Debug)]
219pub struct IntoIter {
220    inner: std::vec::IntoIter<EncodingProposal>,
221}
222
223impl Iterator for IntoIter {
224    type Item = EncodingProposal;
225
226    fn next(&mut self) -> Option<Self::Item> {
227        self.inner.next()
228    }
229
230    #[inline]
231    fn size_hint(&self) -> (usize, Option<usize>) {
232        self.inner.size_hint()
233    }
234}
235
236/// A lending iterator over entries in `AcceptEncoding`.
237#[derive(Debug)]
238pub struct Iter<'a> {
239    inner: slice::Iter<'a, EncodingProposal>,
240}
241
242impl<'a> Iterator for Iter<'a> {
243    type Item = &'a EncodingProposal;
244
245    fn next(&mut self) -> Option<Self::Item> {
246        self.inner.next()
247    }
248
249    #[inline]
250    fn size_hint(&self) -> (usize, Option<usize>) {
251        self.inner.size_hint()
252    }
253}
254
255/// A mutable iterator over entries in `AcceptEncoding`.
256#[derive(Debug)]
257pub struct IterMut<'a> {
258    inner: slice::IterMut<'a, EncodingProposal>,
259}
260
261impl<'a> Iterator for IterMut<'a> {
262    type Item = &'a mut EncodingProposal;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        self.inner.next()
266    }
267
268    #[inline]
269    fn size_hint(&self) -> (usize, Option<usize>) {
270        self.inner.size_hint()
271    }
272}
273
274impl ToHeaderValues for AcceptEncoding {
275    type Iter = option::IntoIter<HeaderValue>;
276    fn to_header_values(&self) -> crate::Result<Self::Iter> {
277        // A HeaderValue will always convert into itself.
278        Ok(self.value().to_header_values().unwrap())
279    }
280}
281
282impl Debug for AcceptEncoding {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        let mut list = f.debug_list();
285        for directive in &self.entries {
286            list.entry(directive);
287        }
288        list.finish()
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use super::*;
295    use crate::content::Encoding;
296    use crate::Response;
297
298    #[test]
299    fn smoke() -> crate::Result<()> {
300        let mut accept = AcceptEncoding::new();
301        accept.push(Encoding::Gzip);
302
303        let mut headers = Response::new(200);
304        accept.apply(&mut headers);
305
306        let accept = AcceptEncoding::from_headers(headers)?.unwrap();
307        assert_eq!(accept.iter().next().unwrap(), Encoding::Gzip);
308        Ok(())
309    }
310
311    #[test]
312    fn wildcard() -> crate::Result<()> {
313        let mut accept = AcceptEncoding::new();
314        accept.set_wildcard(true);
315
316        let mut headers = Response::new(200);
317        accept.apply(&mut headers);
318
319        let accept = AcceptEncoding::from_headers(headers)?.unwrap();
320        assert!(accept.wildcard());
321        Ok(())
322    }
323
324    #[test]
325    fn wildcard_and_header() -> crate::Result<()> {
326        let mut accept = AcceptEncoding::new();
327        accept.push(Encoding::Gzip);
328        accept.set_wildcard(true);
329
330        let mut headers = Response::new(200);
331        accept.apply(&mut headers);
332
333        let accept = AcceptEncoding::from_headers(headers)?.unwrap();
334        assert!(accept.wildcard());
335        assert_eq!(accept.iter().next().unwrap(), Encoding::Gzip);
336        Ok(())
337    }
338
339    #[test]
340    fn iter() -> crate::Result<()> {
341        let mut accept = AcceptEncoding::new();
342        accept.push(Encoding::Gzip);
343        accept.push(Encoding::Brotli);
344
345        let mut headers = Response::new(200);
346        accept.apply(&mut headers);
347
348        let accept = AcceptEncoding::from_headers(headers)?.unwrap();
349        let mut accept = accept.iter();
350        assert_eq!(accept.next().unwrap(), Encoding::Gzip);
351        assert_eq!(accept.next().unwrap(), Encoding::Brotli);
352        Ok(())
353    }
354
355    #[test]
356    fn reorder_based_on_weight() -> crate::Result<()> {
357        let mut accept = AcceptEncoding::new();
358        accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
359        accept.push(EncodingProposal::new(Encoding::Identity, None)?);
360        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
361
362        let mut headers = Response::new(200);
363        accept.apply(&mut headers);
364
365        let mut accept = AcceptEncoding::from_headers(headers)?.unwrap();
366        accept.sort();
367        let mut accept = accept.iter();
368        assert_eq!(accept.next().unwrap(), Encoding::Brotli);
369        assert_eq!(accept.next().unwrap(), Encoding::Gzip);
370        assert_eq!(accept.next().unwrap(), Encoding::Identity);
371        Ok(())
372    }
373
374    #[test]
375    fn reorder_based_on_weight_and_location() -> crate::Result<()> {
376        let mut accept = AcceptEncoding::new();
377        accept.push(EncodingProposal::new(Encoding::Identity, None)?);
378        accept.push(EncodingProposal::new(Encoding::Gzip, None)?);
379        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
380
381        let mut res = Response::new(200);
382        accept.apply(&mut res);
383
384        let mut accept = AcceptEncoding::from_headers(res)?.unwrap();
385        accept.sort();
386        let mut accept = accept.iter();
387        assert_eq!(accept.next().unwrap(), Encoding::Brotli);
388        assert_eq!(accept.next().unwrap(), Encoding::Gzip);
389        assert_eq!(accept.next().unwrap(), Encoding::Identity);
390        Ok(())
391    }
392
393    #[test]
394    fn negotiate() -> crate::Result<()> {
395        let mut accept = AcceptEncoding::new();
396        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
397        accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
398        accept.push(EncodingProposal::new(Encoding::Identity, None)?);
399
400        assert_eq!(
401            accept.negotiate(&[Encoding::Brotli, Encoding::Gzip])?,
402            Encoding::Brotli,
403        );
404        Ok(())
405    }
406
407    #[test]
408    fn negotiate_not_acceptable() -> crate::Result<()> {
409        let mut accept = AcceptEncoding::new();
410        let err = accept.negotiate(&[Encoding::Gzip]).unwrap_err();
411        assert_eq!(err.status(), 406);
412
413        let mut accept = AcceptEncoding::new();
414        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
415        let err = accept.negotiate(&[Encoding::Gzip]).unwrap_err();
416        assert_eq!(err.status(), 406);
417        Ok(())
418    }
419
420    #[test]
421    fn negotiate_wildcard() -> crate::Result<()> {
422        let mut accept = AcceptEncoding::new();
423        accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
424        accept.set_wildcard(true);
425
426        assert_eq!(accept.negotiate(&[Encoding::Gzip])?, Encoding::Gzip);
427        Ok(())
428    }
429}