@@ -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 ) ]
394448struct 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 ) ]
596694pub 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 ( ) , "\x00 value" . 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