| 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 | |