@@ -211,18 +211,53 @@ impl<B: HasExportConfig> WithExportConfig for B {
211
211
}
212
212
213
213
#[ cfg( any( feature = "grpc-tonic" , feature = "http-proto" ) ) ]
214
- fn parse_header_string ( value : & str ) -> impl Iterator < Item = ( & str , & str ) > {
214
+ fn url_decode ( value : & str ) -> Option < String > {
215
+ let mut result = String :: with_capacity ( value. len ( ) ) ;
216
+ let mut chars_to_decode = Vec :: < u8 > :: new ( ) ;
217
+ let mut all_chars = value. chars ( ) ;
218
+
219
+ loop {
220
+ let ch = all_chars. next ( ) ;
221
+
222
+ if ch. is_some ( ) && ch. unwrap ( ) == '%' {
223
+ chars_to_decode. push (
224
+ u8:: from_str_radix ( & format ! ( "{}{}" , all_chars. next( ) ?, all_chars. next( ) ?) , 16 )
225
+ . ok ( ) ?,
226
+ ) ;
227
+ continue ;
228
+ }
229
+
230
+ if !chars_to_decode. is_empty ( ) {
231
+ result. push_str ( std:: str:: from_utf8 ( & chars_to_decode) . ok ( ) ?) ;
232
+ chars_to_decode. clear ( ) ;
233
+ }
234
+
235
+ if let Some ( c) = ch {
236
+ result. push ( c) ;
237
+ } else {
238
+ return Some ( result) ;
239
+ }
240
+ }
241
+ }
242
+
243
+ #[ cfg( any( feature = "grpc-tonic" , feature = "http-proto" ) ) ]
244
+ fn parse_header_string ( value : & str ) -> impl Iterator < Item = ( & str , String ) > {
215
245
value
216
246
. split_terminator ( ',' )
217
247
. map ( str:: trim)
218
248
. filter_map ( parse_header_key_value_string)
219
249
}
220
250
221
251
#[ cfg( any( feature = "grpc-tonic" , feature = "http-proto" ) ) ]
222
- fn parse_header_key_value_string ( key_value_string : & str ) -> Option < ( & str , & str ) > {
252
+ fn parse_header_key_value_string ( key_value_string : & str ) -> Option < ( & str , String ) > {
223
253
key_value_string
224
254
. split_once ( '=' )
225
- . map ( |( key, value) | ( key. trim ( ) , value. trim ( ) ) )
255
+ . map ( |( key, value) | {
256
+ (
257
+ key. trim ( ) ,
258
+ url_decode ( value. trim ( ) ) . unwrap_or ( value. to_string ( ) ) ,
259
+ )
260
+ } )
226
261
. filter ( |( key, value) | !key. is_empty ( ) && !value. is_empty ( ) )
227
262
}
228
263
@@ -267,6 +302,24 @@ mod tests {
267
302
) ;
268
303
}
269
304
305
+ #[ test]
306
+ fn test_url_decode ( ) {
307
+ let test_cases = vec ! [
308
+ // Format: (encoded, expected_decoded)
309
+ ( "v%201" , Some ( "v 1" ) ) ,
310
+ ( "v 1" , Some ( "v 1" ) ) ,
311
+ ( "%C3%B6%C3%A0%C2%A7%C3%96abcd%C3%84" , Some ( "öà§ÖabcdÄ" ) ) ,
312
+ ( "v%XX1" , None ) ,
313
+ ] ;
314
+
315
+ for ( encoded, expected_decoded) in test_cases {
316
+ assert_eq ! (
317
+ super :: url_decode( encoded) ,
318
+ expected_decoded. map( |v| v. to_string( ) ) ,
319
+ )
320
+ }
321
+ }
322
+
270
323
#[ test]
271
324
fn test_parse_header_string ( ) {
272
325
let test_cases = vec ! [
@@ -280,7 +333,10 @@ mod tests {
280
333
for ( input_str, expected_headers) in test_cases {
281
334
assert_eq ! (
282
335
super :: parse_header_string( input_str) . collect:: <Vec <_>>( ) ,
283
- expected_headers,
336
+ expected_headers
337
+ . into_iter( )
338
+ . map( |( k, v) | ( k, v. to_string( ) ) )
339
+ . collect:: <Vec <_>>( ) ,
284
340
)
285
341
}
286
342
}
@@ -290,6 +346,15 @@ mod tests {
290
346
let test_cases = vec ! [
291
347
// Format: (input_str, expected_header)
292
348
( "k1=v1" , Some ( ( "k1" , "v1" ) ) ) ,
349
+ (
350
+ "Authentication=Basic AAA" ,
351
+ Some ( ( "Authentication" , "Basic AAA" ) ) ,
352
+ ) ,
353
+ (
354
+ "Authentication=Basic%20AAA" ,
355
+ Some ( ( "Authentication" , "Basic AAA" ) ) ,
356
+ ) ,
357
+ ( "k1=%XX" , Some ( ( "k1" , "%XX" ) ) ) ,
293
358
( "" , None ) ,
294
359
( "=v1" , None ) ,
295
360
( "k1=" , None ) ,
@@ -298,7 +363,7 @@ mod tests {
298
363
for ( input_str, expected_headers) in test_cases {
299
364
assert_eq ! (
300
365
super :: parse_header_key_value_string( input_str) ,
301
- expected_headers,
366
+ expected_headers. map ( | ( k , v ) | ( k , v . to_string ( ) ) ) ,
302
367
)
303
368
}
304
369
}
0 commit comments