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