headers/common/
transfer_encoding.rs

1use http::HeaderValue;
2
3use crate::util::FlatCsv;
4
5/// `Transfer-Encoding` header, defined in
6/// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1)
7///
8/// The `Transfer-Encoding` header field lists the transfer coding names
9/// corresponding to the sequence of transfer codings that have been (or
10/// will be) applied to the payload body in order to form the message
11/// body.
12///
13/// Note that setting this header will *remove* any previously set
14/// `Content-Length` header, in accordance with
15/// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2):
16///
17/// > A sender MUST NOT send a Content-Length header field in any message
18/// > that contains a Transfer-Encoding header field.
19///
20/// # ABNF
21///
22/// ```text
23/// Transfer-Encoding = 1#transfer-coding
24/// ```
25///
26/// # Example values
27///
28/// * `chunked`
29/// * `gzip, chunked`
30///
31/// # Example
32///
33/// ```
34/// use headers::TransferEncoding;
35///
36/// let transfer = TransferEncoding::chunked();
37/// ```
38// This currently is just a `HeaderValue`, instead of a `Vec<Encoding>`, since
39// the most common by far instance is simply the string `chunked`. It'd be a
40// waste to need to allocate just for that.
41#[derive(Clone, Debug)]
42pub struct TransferEncoding(FlatCsv);
43
44derive_header! {
45    TransferEncoding(_),
46    name: TRANSFER_ENCODING
47}
48
49impl TransferEncoding {
50    /// Constructor for the most common Transfer-Encoding, `chunked`.
51    pub fn chunked() -> TransferEncoding {
52        TransferEncoding(HeaderValue::from_static("chunked").into())
53    }
54
55    /// Returns whether this ends with the `chunked` encoding.
56    pub fn is_chunked(&self) -> bool {
57        self.0
58            .value
59            //TODO(perf): use split and trim (not an actual method) on &[u8]
60            .to_str()
61            .map(|s| {
62                s.split(',')
63                    .next_back()
64                    .map(|encoding| encoding.trim() == "chunked")
65                    .expect("split always has at least 1 item")
66            })
67            .unwrap_or(false)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::super::test_decode;
74    use super::TransferEncoding;
75
76    #[test]
77    fn chunked_is_chunked() {
78        assert!(TransferEncoding::chunked().is_chunked());
79    }
80
81    #[test]
82    fn decode_gzip_chunked_is_chunked() {
83        let te = test_decode::<TransferEncoding>(&["gzip, chunked"]).unwrap();
84        assert!(te.is_chunked());
85    }
86
87    #[test]
88    fn decode_chunked_gzip_is_not_chunked() {
89        let te = test_decode::<TransferEncoding>(&["chunked, gzip"]).unwrap();
90        assert!(!te.is_chunked());
91    }
92
93    #[test]
94    fn decode_notchunked_is_not_chunked() {
95        let te = test_decode::<TransferEncoding>(&["notchunked"]).unwrap();
96        assert!(!te.is_chunked());
97    }
98
99    #[test]
100    fn decode_multiple_is_chunked() {
101        let te = test_decode::<TransferEncoding>(&["gzip", "chunked"]).unwrap();
102        assert!(te.is_chunked());
103    }
104}