1 use std::fmt;
2 
3 #[derive(Clone, Copy, PartialEq, Eq)]
4 pub(crate) struct DecodedLength(u64);
5 
6 #[cfg(any(feature = "http1", feature = "http2"))]
7 impl From<Option<u64>> for DecodedLength {
from(len: Option<u64>) -> Self8     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))]
18 const MAX_LEN: u64 = std::u64::MAX - 2;
19 
20 impl DecodedLength {
21     pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX);
22     pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1);
23     pub(crate) const ZERO: DecodedLength = DecodedLength(0);
24 
25     #[cfg(test)]
new(len: u64) -> Self26     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(feature = "http1")]
danger_len(self) -> u6437     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.
into_opt(self) -> Option<u64>43     pub(crate) fn into_opt(self) -> Option<u64> {
44         match self {
45             DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None,
46             DecodedLength(known) => Some(known),
47         }
48     }
49 
50     /// Checks the `u64` is within the maximum allowed for content-length.
51     #[cfg(any(feature = "http1", feature = "http2"))]
checked_new(len: u64) -> Result<Self, crate::error::Parse>52     pub(crate) fn checked_new(len: u64) -> Result<Self, crate::error::Parse> {
53         use tracing::warn;
54 
55         if len <= MAX_LEN {
56             Ok(DecodedLength(len))
57         } else {
58             warn!("content-length bigger than maximum: {} > {}", len, MAX_LEN);
59             Err(crate::error::Parse::TooLarge)
60         }
61     }
62 
sub_if(&mut self, amt: u64)63     pub(crate) fn sub_if(&mut self, amt: u64) {
64         match *self {
65             DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => (),
66             DecodedLength(ref mut known) => {
67                 *known -= amt;
68             }
69         }
70     }
71 }
72 
73 impl fmt::Debug for DecodedLength {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result74     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75         match *self {
76             DecodedLength::CLOSE_DELIMITED => f.write_str("CLOSE_DELIMITED"),
77             DecodedLength::CHUNKED => f.write_str("CHUNKED"),
78             DecodedLength(n) => f.debug_tuple("DecodedLength").field(&n).finish(),
79         }
80     }
81 }
82 
83 impl fmt::Display for DecodedLength {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result84     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85         match *self {
86             DecodedLength::CLOSE_DELIMITED => f.write_str("close-delimited"),
87             DecodedLength::CHUNKED => f.write_str("chunked encoding"),
88             DecodedLength::ZERO => f.write_str("empty"),
89             DecodedLength(n) => write!(f, "content-length ({} bytes)", n),
90         }
91     }
92 }
93 
94 #[cfg(test)]
95 mod tests {
96     use super::*;
97 
98     #[test]
sub_if_known()99     fn sub_if_known() {
100         let mut len = DecodedLength::new(30);
101         len.sub_if(20);
102 
103         assert_eq!(len.0, 10);
104     }
105 
106     #[test]
sub_if_chunked()107     fn sub_if_chunked() {
108         let mut len = DecodedLength::CHUNKED;
109         len.sub_if(20);
110 
111         assert_eq!(len, DecodedLength::CHUNKED);
112     }
113 }
114