1 | #[cfg (all(feature = "client" , feature = "http1" ))] |
2 | use bytes::BytesMut; |
3 | use http::header::HeaderValue; |
4 | #[cfg (all(feature = "http2" , feature = "client" ))] |
5 | use http::Method; |
6 | #[cfg (any(feature = "client" , all(feature = "server" , feature = "http2" )))] |
7 | use http::{ |
8 | header::{ValueIter, CONTENT_LENGTH}, |
9 | HeaderMap, |
10 | }; |
11 | |
12 | #[cfg (feature = "http1" )] |
13 | pub(super) fn connection_keep_alive(value: &HeaderValue) -> bool { |
14 | connection_has(value, needle:"keep-alive" ) |
15 | } |
16 | |
17 | #[cfg (feature = "http1" )] |
18 | pub(super) fn connection_close(value: &HeaderValue) -> bool { |
19 | connection_has(value, needle:"close" ) |
20 | } |
21 | |
22 | #[cfg (feature = "http1" )] |
23 | fn connection_has(value: &HeaderValue, needle: &str) -> bool { |
24 | if let Ok(s: &str) = value.to_str() { |
25 | for val: &str in s.split(',' ) { |
26 | if val.trim().eq_ignore_ascii_case(needle) { |
27 | return true; |
28 | } |
29 | } |
30 | } |
31 | false |
32 | } |
33 | |
34 | #[cfg (all(feature = "http1" , feature = "server" ))] |
35 | pub(super) fn content_length_parse(value: &HeaderValue) -> Option<u64> { |
36 | from_digits(value.as_bytes()) |
37 | } |
38 | |
39 | #[cfg (any(feature = "client" , all(feature = "server" , feature = "http2" )))] |
40 | pub(super) fn content_length_parse_all(headers: &HeaderMap) -> Option<u64> { |
41 | content_length_parse_all_values(headers.get_all(CONTENT_LENGTH).into_iter()) |
42 | } |
43 | |
44 | #[cfg (any(feature = "client" , all(feature = "server" , feature = "http2" )))] |
45 | pub(super) fn content_length_parse_all_values(values: ValueIter<'_, HeaderValue>) -> Option<u64> { |
46 | // If multiple Content-Length headers were sent, everything can still |
47 | // be alright if they all contain the same value, and all parse |
48 | // correctly. If not, then it's an error. |
49 | |
50 | let mut content_length: Option<u64> = None; |
51 | for h in values { |
52 | if let Ok(line) = h.to_str() { |
53 | for v in line.split(',' ) { |
54 | if let Some(n) = from_digits(v.trim().as_bytes()) { |
55 | if content_length.is_none() { |
56 | content_length = Some(n) |
57 | } else if content_length != Some(n) { |
58 | return None; |
59 | } |
60 | } else { |
61 | return None; |
62 | } |
63 | } |
64 | } else { |
65 | return None; |
66 | } |
67 | } |
68 | |
69 | content_length |
70 | } |
71 | |
72 | fn from_digits(bytes: &[u8]) -> Option<u64> { |
73 | // cannot use FromStr for u64, since it allows a signed prefix |
74 | let mut result = 0u64; |
75 | const RADIX: u64 = 10; |
76 | |
77 | if bytes.is_empty() { |
78 | return None; |
79 | } |
80 | |
81 | for &b in bytes { |
82 | // can't use char::to_digit, since we haven't verified these bytes |
83 | // are utf-8. |
84 | match b { |
85 | b'0' ..=b'9' => { |
86 | result = result.checked_mul(RADIX)?; |
87 | result = result.checked_add((b - b'0' ) as u64)?; |
88 | } |
89 | _ => { |
90 | // not a DIGIT, get outta here! |
91 | return None; |
92 | } |
93 | } |
94 | } |
95 | |
96 | Some(result) |
97 | } |
98 | |
99 | #[cfg (all(feature = "http2" , feature = "client" ))] |
100 | pub(super) fn method_has_defined_payload_semantics(method: &Method) -> bool { |
101 | !matches!( |
102 | *method, |
103 | Method::GET | Method::HEAD | Method::DELETE | Method::CONNECT |
104 | ) |
105 | } |
106 | |
107 | #[cfg (feature = "http2" )] |
108 | pub(super) fn set_content_length_if_missing(headers: &mut HeaderMap, len: u64) { |
109 | headers |
110 | .entry(CONTENT_LENGTH) |
111 | .or_insert_with(|| HeaderValue::from(len)); |
112 | } |
113 | |
114 | #[cfg (all(feature = "client" , feature = "http1" ))] |
115 | pub(super) fn transfer_encoding_is_chunked(headers: &HeaderMap) -> bool { |
116 | is_chunked(encodings:headers.get_all(key:http::header::TRANSFER_ENCODING).into_iter()) |
117 | } |
118 | |
119 | #[cfg (all(feature = "client" , feature = "http1" ))] |
120 | pub(super) fn is_chunked(mut encodings: ValueIter<'_, HeaderValue>) -> bool { |
121 | // chunked must always be the last encoding, according to spec |
122 | if let Some(line: &HeaderValue) = encodings.next_back() { |
123 | return is_chunked_(line); |
124 | } |
125 | |
126 | false |
127 | } |
128 | |
129 | #[cfg (feature = "http1" )] |
130 | pub(super) fn is_chunked_(value: &HeaderValue) -> bool { |
131 | // chunked must always be the last encoding, according to spec |
132 | if let Ok(s: &str) = value.to_str() { |
133 | if let Some(encoding: &str) = s.rsplit(',' ).next() { |
134 | return encoding.trim().eq_ignore_ascii_case("chunked" ); |
135 | } |
136 | } |
137 | |
138 | false |
139 | } |
140 | |
141 | #[cfg (all(feature = "client" , feature = "http1" ))] |
142 | pub(super) fn add_chunked(mut entry: http::header::OccupiedEntry<'_, HeaderValue>) { |
143 | const CHUNKED: &str = "chunked" ; |
144 | |
145 | if let Some(line: &mut HeaderValue) = entry.iter_mut().next_back() { |
146 | // + 2 for ", " |
147 | let new_cap: usize = line.as_bytes().len() + CHUNKED.len() + 2; |
148 | let mut buf: BytesMut = BytesMut::with_capacity(new_cap); |
149 | buf.extend_from_slice(extend:line.as_bytes()); |
150 | buf.extend_from_slice(extend:b", " ); |
151 | buf.extend_from_slice(CHUNKED.as_bytes()); |
152 | |
153 | *line = HeaderValue::from_maybe_shared(buf.freeze()) |
154 | .expect(msg:"original header value plus ascii is valid" ); |
155 | return; |
156 | } |
157 | |
158 | entry.insert(HeaderValue::from_static(CHUNKED)); |
159 | } |
160 | |