hyper/body/
length.rs

1use std::fmt;
2
3#[derive(Clone, Copy, PartialEq, Eq)]
4pub(crate) struct DecodedLength(u64);
5
6#[cfg(any(feature = "http1", feature = "http2"))]
7impl From<Option<u64>> for DecodedLength {
8    fn from(len: Option<u64>) -> Self {
9        len.and_then(|len| {
10            // If the length is u64::MAX, oh well, just reported chunked.
11            Self::checked_new(len).ok()
12        })
13        .unwrap_or(DecodedLength::CHUNKED)
14    }
15}
16
17#[cfg(any(feature = "http1", feature = "http2", test))]
18const MAX_LEN: u64 = u64::MAX - 2;
19
20impl DecodedLength {
21    pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(u64::MAX);
22    pub(crate) const CHUNKED: DecodedLength = DecodedLength(u64::MAX - 1);
23    pub(crate) const ZERO: DecodedLength = DecodedLength(0);
24
25    #[cfg(test)]
26    pub(crate) fn new(len: u64) -> Self {
27        debug_assert!(len <= MAX_LEN);
28        DecodedLength(len)
29    }
30
31    /// Takes the length as a content-length without other checks.
32    ///
33    /// Should only be called if previously confirmed this isn't
34    /// CLOSE_DELIMITED or CHUNKED.
35    #[inline]
36    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
37    pub(crate) fn danger_len(self) -> u64 {
38        debug_assert!(self.0 < Self::CHUNKED.0);
39        self.0
40    }
41
42    /// Converts to an Option<u64> representing a Known or Unknown length.
43    #[cfg(all(
44        any(feature = "http1", feature = "http2"),
45        any(feature = "client", feature = "server")
46    ))]
47    pub(crate) fn into_opt(self) -> Option<u64> {
48        match self {
49            DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None,
50            DecodedLength(known) => Some(known),
51        }
52    }
53
54    /// Checks the `u64` is within the maximum allowed for content-length.
55    #[cfg(any(feature = "http1", feature = "http2"))]
56    pub(crate) fn checked_new(len: u64) -> Result<Self, crate::error::Parse> {
57        if len <= MAX_LEN {
58            Ok(DecodedLength(len))
59        } else {
60            warn!("content-length bigger than maximum: {} > {}", len, MAX_LEN);
61            Err(crate::error::Parse::TooLarge)
62        }
63    }
64
65    #[cfg(all(
66        any(feature = "http1", feature = "http2"),
67        any(feature = "client", feature = "server")
68    ))]
69    pub(crate) fn sub_if(&mut self, amt: u64) {
70        match *self {
71            DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => (),
72            DecodedLength(ref mut known) => {
73                *known -= amt;
74            }
75        }
76    }
77
78    /// Returns whether this represents an exact length.
79    ///
80    /// This includes 0, which of course is an exact known length.
81    ///
82    /// It would return false if "chunked" or otherwise size-unknown.
83    #[cfg(all(any(feature = "client", feature = "server"), feature = "http2"))]
84    pub(crate) fn is_exact(&self) -> bool {
85        self.0 <= MAX_LEN
86    }
87}
88
89impl fmt::Debug for DecodedLength {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match *self {
92            DecodedLength::CLOSE_DELIMITED => f.write_str("CLOSE_DELIMITED"),
93            DecodedLength::CHUNKED => f.write_str("CHUNKED"),
94            DecodedLength(n) => f.debug_tuple("DecodedLength").field(&n).finish(),
95        }
96    }
97}
98
99impl fmt::Display for DecodedLength {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        match *self {
102            DecodedLength::CLOSE_DELIMITED => f.write_str("close-delimited"),
103            DecodedLength::CHUNKED => f.write_str("chunked encoding"),
104            DecodedLength::ZERO => f.write_str("empty"),
105            DecodedLength(n) => write!(f, "content-length ({} bytes)", n),
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn sub_if_known() {
116        let mut len = DecodedLength::new(30);
117        len.sub_if(20);
118
119        assert_eq!(len.0, 10);
120    }
121
122    #[test]
123    fn sub_if_chunked() {
124        let mut len = DecodedLength::CHUNKED;
125        len.sub_if(20);
126
127        assert_eq!(len, DecodedLength::CHUNKED);
128    }
129}