1use std::time::Duration;
6
7use crate::pb::{metadatum, Metadatum};
8use http::{HeaderMap, HeaderName, HeaderValue};
9use tonic::metadata::{
10 AsciiMetadataKey, BinaryMetadataKey, KeyAndValueRef, MetadataMap, MetadataValue,
11};
12
13const DENY: &[&str] = &[
16 "host",
17 "connection",
18 "content-length",
19 "content-type",
20 "keep-alive",
21 "proxy-connection",
22 "transfer-encoding",
23 "te",
24 "upgrade",
25 "grpc-timeout",
26 "grpc-status",
27 "grpc-message",
28 "grpc-status-details-bin",
29 "grpc-encoding",
30 "grpc-accept-encoding",
31];
32
33pub fn is_denied(name: &HeaderName) -> bool {
34 DENY.contains(&name.as_str())
35}
36
37pub fn request_headers_to_metadata(headers: &HeaderMap) -> MetadataMap {
39 let mut filtered = HeaderMap::new();
40 for (name, value) in headers.iter() {
41 if !is_denied(name) {
42 filtered.append(name.clone(), value.clone());
43 }
44 }
45 MetadataMap::from_headers(filtered)
46}
47
48pub fn merge_metadata_into_headers(meta: &MetadataMap, headers: &mut HeaderMap) {
50 for (name, value) in meta.clone().into_headers().iter() {
51 if is_denied(name) {
52 continue;
53 }
54 headers.append(name.clone(), value.clone());
55 }
56}
57
58pub fn metadata_vec_to_metadata(items: &[Metadatum]) -> MetadataMap {
64 let mut md = MetadataMap::new();
65 for m in items {
66 match HeaderName::from_bytes(m.key.as_bytes()) {
67 Ok(n) if !is_denied(&n) => {}
68 _ => continue,
69 }
70 match &m.value {
71 Some(metadatum::Value::AsciiValue(s)) => {
72 if let (Ok(k), Ok(v)) = (
75 m.key.parse::<AsciiMetadataKey>(),
76 MetadataValue::try_from(s.as_str()),
77 ) {
78 md.append(k, v);
79 }
80 }
81 Some(metadatum::Value::BinValue(b)) => {
82 if let Ok(k) = m.key.parse::<BinaryMetadataKey>() {
83 md.append_bin(k, MetadataValue::from_bytes(b));
84 }
85 }
86 None => {}
87 }
88 }
89 md
90}
91
92pub fn metadata_to_vec(meta: &MetadataMap) -> Vec<Metadatum> {
95 let mut out = Vec::new();
96 for kv in meta.iter() {
97 match kv {
98 KeyAndValueRef::Ascii(name, value) => {
99 let key = name.as_str().to_string();
100 if is_denied_str(&key) {
101 continue;
102 }
103 if let Ok(s) = value.to_str() {
104 out.push(Metadatum {
105 key,
106 value: Some(metadatum::Value::AsciiValue(s.to_string())),
107 });
108 }
109 }
110 KeyAndValueRef::Binary(name, value) => {
111 let key = name.as_str().to_string();
112 if is_denied_str(&key) {
113 continue;
114 }
115 if let Ok(bytes) = value.to_bytes() {
116 out.push(Metadatum {
117 key,
118 value: Some(metadatum::Value::BinValue(bytes)),
119 });
120 }
121 }
122 }
123 }
124 out
125}
126
127fn is_denied_str(key: &str) -> bool {
128 DENY.contains(&key)
129}
130
131pub fn parse_grpc_timeout(headers: &HeaderMap) -> Option<Duration> {
133 let raw = headers.get("grpc-timeout")?.to_str().ok()?;
134 let (value, unit) = raw.split_at(raw.len().checked_sub(1)?);
135 let n: u64 = value.parse().ok()?;
136 let d = match unit {
137 "H" => Duration::from_secs(n.checked_mul(3600)?),
138 "M" => Duration::from_secs(n.checked_mul(60)?),
139 "S" => Duration::from_secs(n),
140 "m" => Duration::from_millis(n),
141 "u" => Duration::from_micros(n),
142 "n" => Duration::from_nanos(n),
143 _ => return None,
144 };
145 Some(d)
146}
147
148pub fn format_grpc_timeout(d: Duration) -> HeaderValue {
150 let millis = d.as_millis().min(u128::from(u64::MAX));
151 HeaderValue::from_str(&format!("{millis}m")).expect("valid header value")
152}
153
154pub fn grpc_timeout_from_millis(timeout_millis: u32) -> Option<Duration> {
156 match timeout_millis {
157 0 => None,
158 m => Some(Duration::from_millis(u64::from(m))),
159 }
160}
161
162pub fn read_status(trailers: &HeaderMap, headers: &HeaderMap) -> (u32, String) {
166 let get = |name: &str| trailers.get(name).or_else(|| headers.get(name));
167 let code = get("grpc-status")
168 .and_then(|v| v.to_str().ok())
169 .and_then(|s| s.parse::<u32>().ok())
170 .unwrap_or(0);
171 let message = get("grpc-message")
172 .and_then(|v| v.to_str().ok())
173 .map(percent_decode)
174 .unwrap_or_default();
175 (code, message)
176}
177
178pub fn percent_encode(s: &str) -> String {
180 let mut out = String::with_capacity(s.len());
181 for b in s.bytes() {
182 if b.is_ascii_alphanumeric() || matches!(b, b' ' | b'-' | b'_' | b'.' | b'/' | b':') {
183 out.push(b as char);
184 } else {
185 out.push_str(&format!("%{b:02X}"));
186 }
187 }
188 out
189}
190
191pub fn percent_decode(s: &str) -> String {
193 let bytes = s.as_bytes();
194 let mut out = Vec::with_capacity(bytes.len());
195 let mut i = 0;
196 while i < bytes.len() {
197 if bytes[i] == b'%' && i + 2 < bytes.len() {
198 if let Ok(byte) = u8::from_str_radix(&s[i + 1..i + 3], 16) {
199 out.push(byte);
200 i += 3;
201 continue;
202 }
203 }
204 out.push(bytes[i]);
205 i += 1;
206 }
207 String::from_utf8_lossy(&out).into_owned()
208}