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