1#[cfg(all(feature = "client", feature = "http1"))]
2use bytes::BytesMut;
3use http::header::HeaderValue;
4#[cfg(all(feature = "http2", feature = "client"))]
5use http::Method;
6#[cfg(any(feature = "client", all(feature = "server", feature = "http2")))]
7use http::{
8 header::{ValueIter, CONTENT_LENGTH},
9 HeaderMap,
10};
11
12#[cfg(feature = "http1")]
13pub(super) fn connection_keep_alive(value: &HeaderValue) -> bool {
14 connection_has(value, "keep-alive")
15}
16
17#[cfg(feature = "http1")]
18pub(super) fn connection_close(value: &HeaderValue) -> bool {
19 connection_has(value, "close")
20}
21
22#[cfg(feature = "http1")]
26pub(super) fn connection_any_close(headers: &http::HeaderMap) -> bool {
27 headers
28 .get_all(http::header::CONNECTION)
29 .iter()
30 .any(connection_close)
31}
32
33#[cfg(feature = "http1")]
34fn connection_has(value: &HeaderValue, needle: &str) -> bool {
35 if let Ok(s) = value.to_str() {
36 for val in s.split(',') {
37 if val.trim().eq_ignore_ascii_case(needle) {
38 return true;
39 }
40 }
41 }
42 false
43}
44
45#[cfg(feature = "http1")]
46pub(super) fn te_is_trailers(headers: &http::HeaderMap) -> bool {
47 header_value_list_has(headers.get_all(http::header::TE).into_iter(), "trailers")
48}
49
50#[cfg(feature = "http1")]
51fn header_value_list_has(values: http::header::ValueIter<'_, HeaderValue>, needle: &str) -> bool {
52 for value in values {
53 if let Ok(line) = value.to_str() {
54 for token in line.split(',') {
55 if token.trim().eq_ignore_ascii_case(needle) {
56 return true;
57 }
58 }
59 }
60 }
61
62 false
63}
64
65#[cfg(all(feature = "http1", feature = "server"))]
66pub(super) fn content_length_parse(value: &HeaderValue) -> Option<u64> {
67 from_digits(value.as_bytes())
68}
69
70#[cfg(any(feature = "client", all(feature = "server", feature = "http2")))]
71pub(super) fn content_length_parse_all(headers: &HeaderMap) -> Option<u64> {
72 content_length_parse_all_values(headers.get_all(CONTENT_LENGTH).into_iter())
73}
74
75#[cfg(any(feature = "client", all(feature = "server", feature = "http2")))]
76pub(super) fn content_length_parse_all_values(values: ValueIter<'_, HeaderValue>) -> Option<u64> {
77 let mut content_length: Option<u64> = None;
82 for h in values {
83 if let Ok(line) = h.to_str() {
84 for v in line.split(',') {
85 let n = from_digits(v.trim().as_bytes())?;
86
87 if content_length.is_none() {
88 content_length = Some(n);
89 } else if content_length != Some(n) {
90 return None;
91 }
92 }
93 } else {
94 return None;
95 }
96 }
97
98 content_length
99}
100
101fn from_digits(bytes: &[u8]) -> Option<u64> {
102 let mut result = 0u64;
104 const RADIX: u64 = 10;
105
106 if bytes.is_empty() {
107 return None;
108 }
109
110 for &b in bytes {
111 match b {
114 b'0'..=b'9' => {
115 result = result.checked_mul(RADIX)?;
116 result = result.checked_add(u64::from(b - b'0'))?;
117 }
118 _ => {
119 return None;
121 }
122 }
123 }
124
125 Some(result)
126}
127
128#[cfg(all(feature = "http2", feature = "client"))]
129pub(super) fn method_has_defined_payload_semantics(method: &Method) -> bool {
130 !matches!(
131 *method,
132 Method::GET | Method::HEAD | Method::DELETE | Method::CONNECT
133 )
134}
135
136#[cfg(feature = "http2")]
137pub(super) fn set_content_length_if_missing(headers: &mut HeaderMap, len: u64) {
138 headers
139 .entry(CONTENT_LENGTH)
140 .or_insert_with(|| HeaderValue::from(len));
141}
142
143#[cfg(all(feature = "client", feature = "http1"))]
144pub(super) fn transfer_encoding_is_chunked(headers: &HeaderMap) -> bool {
145 is_chunked(headers.get_all(http::header::TRANSFER_ENCODING).into_iter())
146}
147
148#[cfg(all(feature = "client", feature = "http1"))]
149pub(super) fn is_chunked(mut encodings: ValueIter<'_, HeaderValue>) -> bool {
150 if let Some(line) = encodings.next_back() {
152 return is_chunked_(line);
153 }
154
155 false
156}
157
158#[cfg(feature = "http1")]
159pub(super) fn is_chunked_(value: &HeaderValue) -> bool {
160 if let Ok(s) = value.to_str() {
162 if let Some(encoding) = s.rsplit(',').next() {
163 return encoding.trim().eq_ignore_ascii_case("chunked");
164 }
165 }
166
167 false
168}
169
170#[cfg(all(feature = "client", feature = "http1"))]
171pub(super) fn add_chunked(mut entry: http::header::OccupiedEntry<'_, HeaderValue>) {
172 const CHUNKED: &str = "chunked";
173
174 if let Some(line) = entry.iter_mut().next_back() {
175 let new_cap = line.as_bytes().len() + CHUNKED.len() + 2;
177 let mut buf = BytesMut::with_capacity(new_cap);
178 buf.extend_from_slice(line.as_bytes());
179 buf.extend_from_slice(b", ");
180 buf.extend_from_slice(CHUNKED.as_bytes());
181
182 *line = HeaderValue::from_maybe_shared(buf.freeze())
183 .expect("original header value plus ascii is valid");
184 return;
185 }
186
187 entry.insert(HeaderValue::from_static(CHUNKED));
188}
189
190#[cfg(all(test, feature = "http1"))]
191mod tests {
192 use super::te_is_trailers;
193 use http::header::{HeaderValue, TE};
194 use http::HeaderMap;
195
196 #[test]
197 fn te_is_trailers_accepts_comma_separated_values() {
198 let mut headers = HeaderMap::new();
199 headers.insert(TE, HeaderValue::from_static("gzip, Trailers"));
200
201 assert!(te_is_trailers(&headers));
202 }
203
204 #[test]
205 fn te_is_trailers_accepts_multiple_header_lines() {
206 let mut headers = HeaderMap::new();
207 headers.append(TE, HeaderValue::from_static("gzip"));
208 headers.append(TE, HeaderValue::from_static("trailers"));
209
210 assert!(te_is_trailers(&headers));
211 }
212
213 #[test]
214 fn te_is_trailers_rejects_missing_trailers_token() {
215 let mut headers = HeaderMap::new();
216 headers.insert(TE, HeaderValue::from_static("gzip"));
217
218 assert!(!te_is_trailers(&headers));
219 }
220}