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     /// Returns whether this represents an exact length.
73     ///
74     /// This includes 0, which of course is an exact known length.
75     ///
76     /// It would return false if "chunked" or otherwise size-unknown.
77     #[cfg(feature = "http2")]
is_exact(&self) -> bool78     pub(crate) fn is_exact(&self) -> bool {
79         self.0 <= MAX_LEN
80     }
81 }
82 
83 impl fmt::Debug 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"),
88             DecodedLength(n) => f.debug_tuple("DecodedLength").field(&n).finish(),
89         }
90     }
91 }
92 
93 impl fmt::Display for DecodedLength {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result94     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95         match *self {
96             DecodedLength::CLOSE_DELIMITED => f.write_str("close-delimited"),
97             DecodedLength::CHUNKED => f.write_str("chunked encoding"),
98             DecodedLength::ZERO => f.write_str("empty"),
99             DecodedLength(n) => write!(f, "content-length ({} bytes)", n),
100         }
101     }
102 }
103 
104 #[cfg(test)]
105 mod tests {
106     use super::*;
107 
108     #[test]
sub_if_known()109     fn sub_if_known() {
110         let mut len = DecodedLength::new(30);
111         len.sub_if(20);
112 
113         assert_eq!(len.0, 10);
114     }
115 
116     #[test]
sub_if_chunked()117     fn sub_if_chunked() {
118         let mut len = DecodedLength::CHUNKED;
119         len.sub_if(20);
120 
121         assert_eq!(len, DecodedLength::CHUNKED);
122     }
123 }
124