5
5
using System ;
6
6
using System . Collections . Generic ;
7
7
using System . IO ;
8
- using System . Linq ;
9
8
using System . Net . Http ;
10
9
using System . Threading . Tasks ;
11
10
using Microsoft . Kiota . Abstractions ;
@@ -388,7 +387,9 @@ private async Task ThrowIfFailedResponse(HttpResponseMessage response, Dictionar
388
387
389
388
var statusCodeAsInt = ( int ) response . StatusCode ;
390
389
var statusCodeAsString = statusCodeAsInt . ToString ( ) ;
391
- var responseHeadersDictionary = response . Headers . ToDictionary ( x => x . Key , y => y . Value , StringComparer . OrdinalIgnoreCase ) ;
390
+ var responseHeadersDictionary = new Dictionary < string , IEnumerable < string > > ( StringComparer . OrdinalIgnoreCase ) ;
391
+ foreach ( var header in response . Headers )
392
+ responseHeadersDictionary [ header . Key ] = header . Value ;
392
393
ParsableFactory < IParsable > ? errorFactory ;
393
394
if ( errorMapping == null ||
394
395
! errorMapping . TryGetValue ( statusCodeAsString , out errorFactory ) &&
@@ -470,51 +471,86 @@ private async Task<HttpResponseMessage> GetHttpResponseMessage(RequestInformatio
470
471
var ex = new InvalidOperationException ( "Could not get a response after calling the service" ) ;
471
472
throw ex ;
472
473
}
473
- if ( response . Headers . TryGetValues ( "Content-Length" , out var contentLengthValues ) &&
474
- contentLengthValues . Any ( ) &&
475
- contentLengthValues . First ( ) is string firstContentLengthValue &&
476
- int . TryParse ( firstContentLengthValue , out var contentLength ) )
474
+ if ( response . Headers . TryGetValues ( "Content-Length" , out var contentLengthValues ) )
477
475
{
478
- activityForAttributes ? . SetTag ( "http.response_content_length" , contentLength ) ;
476
+ using var contentLengthEnumerator = contentLengthValues . GetEnumerator ( ) ;
477
+ if ( contentLengthEnumerator . MoveNext ( ) && int . TryParse ( contentLengthEnumerator . Current , out var contentLength ) )
478
+ {
479
+ activityForAttributes ? . SetTag ( "http.response_content_length" , contentLength ) ;
480
+ }
479
481
}
480
- if ( response . Headers . TryGetValues ( "Content-Type" , out var contentTypeValues ) &&
481
- contentTypeValues . Any ( ) &&
482
- contentTypeValues . First ( ) is string firstContentTypeValue )
482
+ if ( response . Headers . TryGetValues ( "Content-Type" , out var contentTypeValues ) )
483
483
{
484
- activityForAttributes ? . SetTag ( "http.response_content_type" , firstContentTypeValue ) ;
484
+ using var contentTypeEnumerator = contentTypeValues . GetEnumerator ( ) ;
485
+ if ( contentTypeEnumerator . MoveNext ( ) )
486
+ {
487
+ activityForAttributes ? . SetTag ( "http.response_content_type" , contentTypeEnumerator . Current ) ;
488
+ }
485
489
}
486
490
activityForAttributes ? . SetTag ( "http.status_code" , ( int ) response . StatusCode ) ;
487
491
activityForAttributes ? . SetTag ( "http.flavor" , $ "{ response . Version . Major } .{ response . Version . Minor } ") ;
488
492
489
493
return await RetryCAEResponseIfRequired ( response , requestInfo , cancellationToken , claims , activityForAttributes ) . ConfigureAwait ( false ) ;
490
494
}
495
+
491
496
private static readonly Regex caeValueRegex = new ( "\" ([^\" ]*)\" " , RegexOptions . Compiled , TimeSpan . FromMilliseconds ( 100 ) ) ;
497
+
492
498
/// <summary>
493
499
/// The key for the event raised by tracing when an authentication challenge is received
494
500
/// </summary>
495
501
public const string AuthenticateChallengedEventKey = "com.microsoft.kiota.authenticate_challenge_received" ;
502
+
496
503
private async Task < HttpResponseMessage > RetryCAEResponseIfRequired ( HttpResponseMessage response , RequestInformation requestInfo , CancellationToken cancellationToken , string ? claims , Activity ? activityForAttributes )
497
504
{
498
505
using var span = activitySource ? . StartActivity ( nameof ( RetryCAEResponseIfRequired ) ) ;
499
506
if ( response . StatusCode == HttpStatusCode . Unauthorized &&
500
507
string . IsNullOrEmpty ( claims ) && // avoid infinite loop, we only retry once
501
- ( requestInfo . Content ? . CanSeek ?? true ) &&
502
- response . Headers . WwwAuthenticate ? . FirstOrDefault ( filterAuthHeader ) is AuthenticationHeaderValue authHeader &&
503
- authHeader . Parameter ? . Split ( new char [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries )
504
- . Select ( static x => x . Trim ( ) )
505
- . FirstOrDefault ( static x => x . StartsWith ( ClaimsKey , StringComparison . OrdinalIgnoreCase ) ) is string rawResponseClaims &&
506
- caeValueRegex . Match ( rawResponseClaims ) is Match claimsMatch &&
507
- claimsMatch . Groups . Count > 1 &&
508
- claimsMatch . Groups [ 1 ] . Value is string responseClaims )
508
+ ( requestInfo . Content ? . CanSeek ?? true ) )
509
509
{
510
- span ? . AddEvent ( new ActivityEvent ( AuthenticateChallengedEventKey ) ) ;
511
- activityForAttributes ? . SetTag ( "http.retry_count" , 1 ) ;
512
- requestInfo . Content ? . Seek ( 0 , SeekOrigin . Begin ) ;
513
- await DrainAsync ( response , cancellationToken ) . ConfigureAwait ( false ) ;
514
- return await GetHttpResponseMessage ( requestInfo , cancellationToken , activityForAttributes , responseClaims ) . ConfigureAwait ( false ) ;
510
+ AuthenticationHeaderValue ? authHeader = null ;
511
+ foreach ( var header in response . Headers . WwwAuthenticate )
512
+ {
513
+ if ( filterAuthHeader ( header ) )
514
+ {
515
+ authHeader = header ;
516
+ break ;
517
+ }
518
+ }
519
+
520
+ if ( authHeader is not null )
521
+ {
522
+ var authHeaderParameters = authHeader . Parameter ? . Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
523
+
524
+ string ? rawResponseClaims = null ;
525
+ if ( authHeaderParameters != null )
526
+ {
527
+ foreach ( var parameter in authHeaderParameters )
528
+ {
529
+ var trimmedParameter = parameter . Trim ( ) ;
530
+ if ( trimmedParameter . StartsWith ( ClaimsKey , StringComparison . OrdinalIgnoreCase ) )
531
+ {
532
+ rawResponseClaims = trimmedParameter ;
533
+ break ;
534
+ }
535
+ }
536
+ }
537
+
538
+ if ( rawResponseClaims != null &&
539
+ caeValueRegex . Match ( rawResponseClaims ) is Match claimsMatch &&
540
+ claimsMatch . Groups . Count > 1 &&
541
+ claimsMatch . Groups [ 1 ] . Value is string responseClaims )
542
+ {
543
+ span ? . AddEvent ( new ActivityEvent ( AuthenticateChallengedEventKey ) ) ;
544
+ activityForAttributes ? . SetTag ( "http.retry_count" , 1 ) ;
545
+ requestInfo . Content ? . Seek ( 0 , SeekOrigin . Begin ) ;
546
+ await DrainAsync ( response , cancellationToken ) . ConfigureAwait ( false ) ;
547
+ return await GetHttpResponseMessage ( requestInfo , cancellationToken , activityForAttributes , responseClaims ) . ConfigureAwait ( false ) ;
548
+ }
549
+ }
515
550
}
516
551
return response ;
517
552
}
553
+
518
554
private void SetBaseUrlForRequestInformation ( RequestInformation requestInfo )
519
555
{
520
556
IDictionaryExtensions . AddOrReplace ( requestInfo . PathParameters , "baseurl" , BaseUrl ! ) ;
@@ -527,6 +563,7 @@ private void SetBaseUrlForRequestInformation(RequestInformation requestInfo)
527
563
return result ;
528
564
else throw new InvalidOperationException ( $ "Could not convert the request information to a { typeof ( T ) . Name } ") ;
529
565
}
566
+
530
567
private HttpRequestMessage GetRequestMessageFromRequestInformation ( RequestInformation requestInfo , Activity ? activityForAttributes )
531
568
{
532
569
using var span = activitySource ? . StartActivity ( nameof ( GetRequestMessageFromRequestInformation ) ) ;
@@ -544,37 +581,42 @@ private HttpRequestMessage GetRequestMessageFromRequestInformation(RequestInform
544
581
Version = new Version ( 2 , 0 )
545
582
} ;
546
583
547
- if ( requestInfo . RequestOptions . Any ( ) )
584
+ if ( requestInfo . RequestOptions != null )
548
585
#if NET5_0_OR_GREATER
549
586
{
550
- requestInfo . RequestOptions . ToList ( ) . ForEach ( x => message . Options . Set ( new HttpRequestOptionsKey < IRequestOption > ( x . GetType ( ) . FullName ! ) , x ) ) ;
587
+ foreach ( var option in requestInfo . RequestOptions )
588
+ message . Options . Set ( new HttpRequestOptionsKey < IRequestOption > ( option . GetType ( ) . FullName ! ) , option ) ;
551
589
}
552
590
message . Options . Set ( new HttpRequestOptionsKey < IRequestOption > ( typeof ( ObservabilityOptions ) . FullName ! ) , obsOptions ) ;
553
591
#else
554
592
{
555
- requestInfo . RequestOptions . ToList ( ) . ForEach ( x => IDictionaryExtensions . TryAdd ( message . Properties , x . GetType ( ) . FullName ! , x ) ) ;
593
+ foreach ( var option in requestInfo . RequestOptions )
594
+ IDictionaryExtensions . TryAdd ( message . Properties , option . GetType ( ) . FullName ! , option ) ;
556
595
}
557
596
IDictionaryExtensions . TryAdd ( message . Properties ! , typeof ( ObservabilityOptions ) . FullName , obsOptions ) ;
558
597
#endif
559
598
560
599
if ( requestInfo . Content != null && requestInfo . Content != Stream . Null )
561
600
message . Content = new StreamContent ( requestInfo . Content ) ;
562
- if ( requestInfo . Headers ? . Any ( ) ?? false )
601
+ if ( requestInfo . Headers != null )
563
602
foreach ( var header in requestInfo . Headers )
564
603
if ( ! message . Headers . TryAddWithoutValidation ( header . Key , header . Value ) && message . Content != null )
565
604
message . Content . Headers . TryAddWithoutValidation ( header . Key , header . Value ) ; // Try to add the headers we couldn't add to the HttpRequestMessage before to the HttpContent
566
605
567
606
if ( message . Content != null )
568
607
{
569
- if ( message . Content . Headers . TryGetValues ( "Content-Length" , out var contentLenValues ) &&
570
- contentLenValues . Any ( ) &&
571
- contentLenValues . First ( ) is string contentLenValue &&
572
- int . TryParse ( contentLenValue , out var contentLenValueInt ) )
573
- activityForAttributes ? . SetTag ( "http.request_content_length" , contentLenValueInt ) ;
574
- if ( message . Content . Headers . TryGetValues ( "Content-Type" , out var contentTypeValues ) &&
575
- contentTypeValues . Any ( ) &&
576
- contentTypeValues . First ( ) is string contentTypeValue )
577
- activityForAttributes ? . SetTag ( "http.request_content_type" , contentTypeValue ) ;
608
+ if ( message . Content . Headers . TryGetValues ( "Content-Length" , out var contentLenValues ) )
609
+ {
610
+ var contentLenEnumerator = contentLenValues . GetEnumerator ( ) ;
611
+ if ( contentLenEnumerator . MoveNext ( ) && int . TryParse ( contentLenEnumerator . Current , out var contentLenValueInt ) )
612
+ activityForAttributes ? . SetTag ( "http.request_content_length" , contentLenValueInt ) ;
613
+ }
614
+ if ( message . Content . Headers . TryGetValues ( "Content-Type" , out var contentTypeValues ) )
615
+ {
616
+ var contentTypeEnumerator = contentTypeValues . GetEnumerator ( ) ;
617
+ if ( contentTypeEnumerator . MoveNext ( ) )
618
+ activityForAttributes ? . SetTag ( "http.request_content_type" , contentTypeEnumerator . Current ) ;
619
+ }
578
620
}
579
621
return message ;
580
622
}
0 commit comments