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 { |
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(default: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)] |
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 (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 | 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" ))] |
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 | |
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" )] |
78 | pub(crate) fn is_exact(&self) -> bool { |
79 | self.0 <= MAX_LEN |
80 | } |
81 | } |
82 | |
83 | impl fmt::Debug for DecodedLength { |
84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
85 | match *self { |
86 | DecodedLength::CLOSE_DELIMITED => f.write_str(data:"CLOSE_DELIMITED" ), |
87 | DecodedLength::CHUNKED => f.write_str(data:"CHUNKED" ), |
88 | DecodedLength(n: u64) => f.debug_tuple(name:"DecodedLength" ).field(&n).finish(), |
89 | } |
90 | } |
91 | } |
92 | |
93 | impl fmt::Display for DecodedLength { |
94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
95 | match *self { |
96 | DecodedLength::CLOSE_DELIMITED => f.write_str(data:"close-delimited" ), |
97 | DecodedLength::CHUNKED => f.write_str(data:"chunked encoding" ), |
98 | DecodedLength::ZERO => f.write_str(data:"empty" ), |
99 | DecodedLength(n: u64) => write!(f, "content-length ( {} bytes)" , n), |
100 | } |
101 | } |
102 | } |
103 | |
104 | #[cfg (test)] |
105 | mod tests { |
106 | use super::*; |
107 | |
108 | #[test ] |
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 ] |
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 | |