Skip to content

Commit 170767f

Browse files
committed
store already-parsed metadata, return an error when invalid
1 parent 7d04df9 commit 170767f

File tree

1 file changed

+191
-30
lines changed

1 file changed

+191
-30
lines changed

client/src/lib.rs

Lines changed: 191 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,18 @@ pub struct ClientOptions {
149149
pub keep_alive: Option<ClientKeepAliveConfig>,
150150

151151
/// HTTP headers to include on every RPC call.
152+
///
153+
/// These must be valid gRPC metadata keys, and must not end with a `-bin` suffix (to set binary
154+
/// headers, see [ClientOptions::binary_headers]). Invalid header keys will cause an error to be
155+
/// returned when connecting.
152156
#[builder(default)]
153157
pub headers: Option<HashMap<String, String>>,
154158

155159
/// HTTP headers to include on every RPC call as binary gRPC metadata (typically encoded as
156160
/// base64).
157161
///
158-
/// These must be valid binary gRPC metadata keys (and end with a `-bin` suffix).
162+
/// These must be valid binary gRPC metadata keys (and end with a `-bin` suffix). Invalid
163+
/// header keys will cause an error to be returned when connecting.
159164
#[builder(default)]
160165
pub binary_headers: Option<HashMap<String, Vec<u8>>>,
161166

@@ -332,6 +337,9 @@ pub enum ClientInitError {
332337
/// Invalid URI. Configuration error, fatal.
333338
#[error("Invalid URI: {0:?}")]
334339
InvalidUri(#[from] InvalidUri),
340+
/// Invalid gRPC metadata headers. Configuration error.
341+
#[error("Invalid headers: {0:?}")]
342+
InvalidHeaders(#[from] InvalidHeaderError),
335343
/// Server connection error. Crashing and restarting the worker is likely best.
336344
#[error("Server connection error: {0:?}")]
337345
TonicTransportError(#[from] tonic::transport::Error),
@@ -341,6 +349,37 @@ pub enum ClientInitError {
341349
SystemInfoCallError(tonic::Status),
342350
}
343351

352+
/// Errors thrown when a gRPC metadata header is invalid.
353+
#[derive(thiserror::Error, Debug)]
354+
pub enum InvalidHeaderError {
355+
/// A binary header key was invalid
356+
#[error("Invalid binary header key '{key}': {source}")]
357+
InvalidBinaryHeaderKey {
358+
/// The invalid key
359+
key: String,
360+
/// The source error from tonic
361+
source: tonic::metadata::errors::InvalidMetadataKey,
362+
},
363+
/// An ASCII header key was invalid
364+
#[error("Invalid ASCII header key '{key}': {source}")]
365+
InvalidAsciiHeaderKey {
366+
/// The invalid key
367+
key: String,
368+
/// The source error from tonic
369+
source: tonic::metadata::errors::InvalidMetadataKey,
370+
},
371+
/// An ASCII header value was invalid
372+
#[error("Invalid ASCII header value for key '{key}': {source}")]
373+
InvalidAsciiHeaderValue {
374+
/// The key
375+
key: String,
376+
/// The invalid value
377+
value: String,
378+
/// The source error from tonic
379+
source: tonic::metadata::errors::InvalidMetadataValue,
380+
},
381+
}
382+
344383
/// A client with [ClientOptions] attached, which can be passed to initialize workers,
345384
/// or can be used directly. Is cheap to clone.
346385
#[derive(Clone, Debug)]
@@ -357,15 +396,30 @@ impl<C> ConfiguredClient<C> {
357396
/// Set HTTP request headers overwriting previous headers.
358397
///
359398
/// This will not affect headers set via [ClientOptions::binary_headers].
360-
pub fn set_headers(&self, headers: HashMap<String, String>) {
361-
self.headers.write().user_headers = headers;
399+
///
400+
/// # Errors
401+
///
402+
/// Will return an error if any of the provided keys or values are not valid gRPC metadata.
403+
/// If an error is returned, the previous headers will remain unchanged.
404+
pub fn set_headers(&self, headers: HashMap<String, String>) -> Result<(), InvalidHeaderError> {
405+
self.headers.write().user_headers = parse_ascii_headers(headers)?;
406+
Ok(())
362407
}
363408

364409
/// Set binary HTTP request headers overwriting previous headers.
365410
///
366411
/// This will not affect headers set via [ClientOptions::headers].
367-
pub fn set_binary_headers(&self, binary_headers: HashMap<String, Vec<u8>>) {
368-
self.headers.write().user_binary_headers = binary_headers;
412+
///
413+
/// # Errors
414+
///
415+
/// Will return an error if any of the provided keys are not valid gRPC binary metadata keys.
416+
/// If an error is returned, the previous headers will remain unchanged.
417+
pub fn set_binary_headers(
418+
&self,
419+
binary_headers: HashMap<String, Vec<u8>>,
420+
) -> Result<(), InvalidHeaderError> {
421+
self.headers.write().user_binary_headers = parse_binary_headers(binary_headers)?;
422+
Ok(())
369423
}
370424

371425
/// Set API key, overwriting previous
@@ -392,8 +446,8 @@ impl<C> ConfiguredClient<C> {
392446

393447
#[derive(Debug)]
394448
struct ClientHeaders {
395-
user_headers: HashMap<String, String>,
396-
user_binary_headers: HashMap<String, Vec<u8>>,
449+
user_headers: HashMap<AsciiMetadataKey, AsciiMetadataValue>,
450+
user_binary_headers: HashMap<BinaryMetadataKey, BinaryMetadataValue>,
397451
api_key: Option<String>,
398452
}
399453

@@ -402,22 +456,13 @@ impl ClientHeaders {
402456
for (key, val) in self.user_headers.iter() {
403457
// Only if not already present
404458
if !metadata.contains_key(key) {
405-
// Ignore invalid keys/values
406-
if let (Ok(key), Ok(val)) = (
407-
AsciiMetadataKey::from_str(key),
408-
AsciiMetadataValue::from_str(val),
409-
) {
410-
metadata.insert(key, val);
411-
}
459+
metadata.insert(key, val.clone());
412460
}
413461
}
414462
for (key, val) in self.user_binary_headers.iter() {
415463
// Only if not already present
416464
if !metadata.contains_key(key) {
417-
// Ignore invalid keys
418-
if let Ok(key) = BinaryMetadataKey::from_str(key) {
419-
metadata.insert_bin(key, BinaryMetadataValue::from_bytes(val));
420-
}
465+
metadata.insert_bin(key, val.clone());
421466
}
422467
}
423468
if let Some(api_key) = &self.api_key {
@@ -523,8 +568,10 @@ impl ClientOptions {
523568
};
524569

525570
let headers = Arc::new(RwLock::new(ClientHeaders {
526-
user_headers: self.headers.clone().unwrap_or_default(),
527-
user_binary_headers: self.binary_headers.clone().unwrap_or_default(),
571+
user_headers: parse_ascii_headers(self.headers.clone().unwrap_or_default())?,
572+
user_binary_headers: parse_binary_headers(
573+
self.binary_headers.clone().unwrap_or_default(),
574+
)?,
528575
api_key: self.api_key.clone(),
529576
}));
530577
let interceptor = ServiceCallInterceptor {
@@ -591,6 +638,57 @@ impl ClientOptions {
591638
}
592639
}
593640

641+
fn parse_ascii_headers(
642+
headers: HashMap<String, String>,
643+
) -> Result<HashMap<AsciiMetadataKey, AsciiMetadataValue>, InvalidHeaderError> {
644+
let mut parsed_headers = HashMap::with_capacity(headers.len());
645+
for (k, v) in headers.into_iter() {
646+
let key = match AsciiMetadataKey::from_str(&k) {
647+
Ok(key) => key,
648+
Err(err) => {
649+
return Err(InvalidHeaderError::InvalidAsciiHeaderKey {
650+
key: k,
651+
source: err,
652+
});
653+
}
654+
};
655+
let value = match MetadataValue::from_str(&v) {
656+
Ok(value) => value,
657+
Err(err) => {
658+
return Err(InvalidHeaderError::InvalidAsciiHeaderValue {
659+
key: k,
660+
value: v,
661+
source: err,
662+
});
663+
}
664+
};
665+
parsed_headers.insert(key, value);
666+
}
667+
668+
Ok(parsed_headers)
669+
}
670+
671+
fn parse_binary_headers(
672+
headers: HashMap<String, Vec<u8>>,
673+
) -> Result<HashMap<BinaryMetadataKey, BinaryMetadataValue>, InvalidHeaderError> {
674+
let mut parsed_headers = HashMap::with_capacity(headers.len());
675+
for (k, v) in headers.into_iter() {
676+
let key = match BinaryMetadataKey::from_str(&k) {
677+
Ok(key) => key,
678+
Err(err) => {
679+
return Err(InvalidHeaderError::InvalidBinaryHeaderKey {
680+
key: k,
681+
source: err,
682+
});
683+
}
684+
};
685+
let value = BinaryMetadataValue::from_bytes(&v);
686+
parsed_headers.insert(key, value);
687+
}
688+
689+
Ok(parsed_headers)
690+
}
691+
594692
/// Interceptor which attaches common metadata (like "client-name") to every outgoing call
595693
#[derive(Clone)]
596694
pub struct ServiceCallInterceptor {
@@ -1806,11 +1904,14 @@ mod tests {
18061904
user_binary_headers: HashMap::new(),
18071905
api_key: Some("my-api-key".to_owned()),
18081906
}));
1809-
headers
1810-
.clone()
1811-
.write()
1812-
.user_headers
1813-
.insert("my-meta-key".to_owned(), "my-meta-val".to_owned());
1907+
headers.clone().write().user_headers.insert(
1908+
"my-meta-key".parse().unwrap(),
1909+
"my-meta-val".parse().unwrap(),
1910+
);
1911+
headers.clone().write().user_binary_headers.insert(
1912+
"my-bin-meta-key-bin".parse().unwrap(),
1913+
vec![1, 2, 3].try_into().unwrap(),
1914+
);
18141915
let mut interceptor = ServiceCallInterceptor {
18151916
opts,
18161917
headers: headers.clone(),
@@ -1823,33 +1924,44 @@ mod tests {
18231924
req.metadata().get("authorization").unwrap(),
18241925
"Bearer my-api-key"
18251926
);
1927+
assert_eq!(
1928+
req.metadata().get_bin("my-bin-meta-key-bin").unwrap(),
1929+
vec![1, 2, 3].as_slice()
1930+
);
18261931

18271932
// Overwrite at request time
18281933
let mut req = tonic::Request::new(());
18291934
req.metadata_mut()
18301935
.insert("my-meta-key", "my-meta-val2".parse().unwrap());
18311936
req.metadata_mut()
18321937
.insert("authorization", "my-api-key2".parse().unwrap());
1938+
req.metadata_mut()
1939+
.insert_bin("my-bin-meta-key-bin", vec![4, 5, 6].try_into().unwrap());
18331940
let req = interceptor.call(req).unwrap();
18341941
assert_eq!(req.metadata().get("my-meta-key").unwrap(), "my-meta-val2");
18351942
assert_eq!(req.metadata().get("authorization").unwrap(), "my-api-key2");
1943+
assert_eq!(
1944+
req.metadata().get_bin("my-bin-meta-key-bin").unwrap(),
1945+
vec![4, 5, 6].as_slice()
1946+
);
18361947

18371948
// Overwrite auth on header
1838-
headers
1839-
.clone()
1840-
.write()
1841-
.user_headers
1842-
.insert("authorization".to_owned(), "my-api-key3".to_owned());
1949+
headers.clone().write().user_headers.insert(
1950+
"authorization".parse().unwrap(),
1951+
"my-api-key3".parse().unwrap(),
1952+
);
18431953
let req = interceptor.call(tonic::Request::new(())).unwrap();
18441954
assert_eq!(req.metadata().get("my-meta-key").unwrap(), "my-meta-val");
18451955
assert_eq!(req.metadata().get("authorization").unwrap(), "my-api-key3");
18461956

18471957
// Remove headers and auth and confirm gone
18481958
headers.clone().write().user_headers.clear();
1959+
headers.clone().write().user_binary_headers.clear();
18491960
headers.clone().write().api_key.take();
18501961
let req = interceptor.call(tonic::Request::new(())).unwrap();
18511962
assert!(!req.metadata().contains_key("my-meta-key"));
18521963
assert!(!req.metadata().contains_key("authorization"));
1964+
assert!(!req.metadata().contains_key("my-bin-meta-key-bin"));
18531965

18541966
// Timeout header not overriden
18551967
let mut req = tonic::Request::new(());
@@ -1862,6 +1974,55 @@ mod tests {
18621974
);
18631975
}
18641976

1977+
#[test]
1978+
fn invalid_ascii_header_key() {
1979+
let invalid_headers = {
1980+
let mut h = HashMap::new();
1981+
h.insert("x-binary-key-bin".to_owned(), "value".to_owned());
1982+
h
1983+
};
1984+
1985+
let result = parse_ascii_headers(invalid_headers);
1986+
assert!(result.is_err());
1987+
assert_eq!(
1988+
result.err().unwrap().to_string(),
1989+
"Invalid ASCII header key 'x-binary-key-bin': invalid gRPC metadata key name"
1990+
);
1991+
}
1992+
1993+
#[test]
1994+
fn invalid_ascii_header_value() {
1995+
let invalid_headers = {
1996+
let mut h = HashMap::new();
1997+
// Nul bytes are valid UTF-8, but not valid ascii gRPC headers:
1998+
h.insert("x-ascii-key".to_owned(), "\x00value".to_owned());
1999+
h
2000+
};
2001+
2002+
let result = parse_ascii_headers(invalid_headers);
2003+
assert!(result.is_err());
2004+
assert_eq!(
2005+
result.err().unwrap().to_string(),
2006+
"Invalid ASCII header value for key 'x-ascii-key': failed to parse metadata value"
2007+
);
2008+
}
2009+
2010+
#[test]
2011+
fn invalid_binary_header_key() {
2012+
let invalid_headers = {
2013+
let mut h = HashMap::new();
2014+
h.insert("x-ascii-key".to_owned(), vec![1, 2, 3]);
2015+
h
2016+
};
2017+
2018+
let result = parse_binary_headers(invalid_headers);
2019+
assert!(result.is_err());
2020+
assert_eq!(
2021+
result.err().unwrap().to_string(),
2022+
"Invalid binary header key 'x-ascii-key': invalid gRPC metadata key name"
2023+
);
2024+
}
2025+
18652026
#[test]
18662027
fn keep_alive_defaults() {
18672028
let mut builder = ClientOptionsBuilder::default();

0 commit comments

Comments
 (0)