@@ -24,7 +24,7 @@ namespace Microsoft.PowerShell.Commands
24
24
{
25
25
/// <summary>
26
26
/// Exception class for webcmdlets to enable returning HTTP error response
27
- /// </summary>
27
+ /// </summary>
28
28
public sealed class HttpResponseException : HttpRequestException
29
29
{
30
30
/// <summary>
@@ -48,6 +48,22 @@ public HttpResponseException (string message, HttpResponseMessage response) : ba
48
48
/// </summary>
49
49
public abstract partial class WebRequestPSCmdlet : PSCmdlet
50
50
{
51
+
52
+ /// <summary>
53
+ /// gets or sets the PreserveAuthorizationOnRedirect property
54
+ /// </summary>
55
+ /// <remarks>
56
+ /// This property overrides compatibility with web requests on Windows.
57
+ /// On FullCLR (WebRequest), authorization headers are stripped during redirect.
58
+ /// CoreCLR (HTTPClient) does not have this behavior so web requests that work on
59
+ /// PowerShell/FullCLR can fail with PowerShell/CoreCLR. To provide compatibility,
60
+ /// we'll detect requests with an Authorization header and automatically strip
61
+ /// the header when the first redirect occurs. This switch turns off this logic for
62
+ /// edge cases where the authorization header needs to be preserved across redirects.
63
+ /// </remarks>
64
+ [ Parameter ]
65
+ public virtual SwitchParameter PreserveAuthorizationOnRedirect { get ; set ; }
66
+
8000
51
67
#region Abstract Methods
52
68
53
69
/// <summary>
@@ -111,7 +127,9 @@ private HttpMethod GetHttpMethod(WebRequestMethod method)
111
127
112
128
#region Virtual Methods
113
129
114
- internal virtual HttpClient GetHttpClient ( )
130
+ // NOTE: Only pass true for handleRedirect if the original request has an authorization header
131
+ // and PreserveAuthorizationOnRedirect is NOT set.
132
+ internal virtual HttpClient GetHttpClient ( bool handleRedirect )
115
133
{
116
134
// By default the HttpClientHandler will automatically decompress GZip and Deflate content
117
135
HttpClientHandler handler = new HttpClientHandler ( ) ;
@@ -150,7 +168,12 @@ internal virtual HttpClient GetHttpClient()
150
168
handler . ServerCertificateCustomValidationCallback = HttpClientHandler . DangerousAcceptAnyServerCertificateValidator ;
151
169
}
152
170
153
- if ( WebSession . MaximumRedirection > - 1 )
171
+ // This indicates GetResponse will handle redirects.
172
+ if ( handleRedirect )
173
+ {
174
+ handler . AllowAutoRedirect = false ;
175
+ }
176
+ else if ( WebSession . MaximumRedirection > - 1 )
154
177
{
155
178
if ( WebSession . MaximumRedirection == 0 )
156
179
{
@@ -178,7 +201,7 @@ internal virtual HttpClient GetHttpClient()
178
201
return httpClient ;
179
202
}
180
203
181
- internal virtual HttpRequestMessage GetRequest ( Uri uri )
204
+ internal virtual HttpRequestMessage GetRequest ( Uri uri , bool stripAuthorization )
182
205
{
183
206
Uri requestUri = PrepareUri ( uri ) ;
184
207
HttpMethod httpMethod = null ;
@@ -217,6 +240,13 @@ internal virtual HttpRequestMessage GetRequest(Uri uri)
217
240
}
218
241
else
219
242
{
243
+ if ( stripAuthorization
244
+<
67E6
/span> &&
245
+ String . Equals ( entry . Key , HttpKnownHeaderNames . Authorization . ToString ( ) , StringComparison . OrdinalIgnoreCase )
246
+ )
247
+ {
248
+ continue ;
249
+ }
220
250
request . Headers . Add ( entry . Key , entry . Value ) ;
221
251
}
222
252
}
@@ -367,13 +397,77 @@ internal virtual void FillRequestStream(HttpRequestMessage request)
367
397
}
368
398
}
369
399
370
- internal virtual HttpResponseMessage GetResponse ( HttpClient client , HttpRequestMessage request )
400
+ // Returns true if the status code is one of the supported redirection codes.
401
+ static bool IsRedirectCode ( HttpStatusCode code )
402
+ {
403
+ int intCode = ( int )
A3E2
code ;
404
+ return
405
+ (
406
+ ( intCode >= 300 && intCode < 304 )
407
+ ||
408
+ intCode == 307
409
+ ) ;
410
+ }
411
+
412
+ // Returns true if the status code is a redirection code and the action requires switching from POST to GET on redirection.
413
+ // NOTE: Some of these status codes map to the same underlying value but spelling them out for completeness.
414
+ static bool IsRedirectToGet ( HttpStatusCode code )
415
+ {
416
+ return
417
+ (
418
+ code == HttpStatusCode . Found
419
+ ||
420
+ code == HttpStatusCode . Moved
421
+ ||
422
+ code == HttpStatusCode . Redirect
423
+ ||
424
+ code == HttpStatusCode . RedirectMethod
425
+ ||
426
+ code == HttpStatusCode . TemporaryRedirect
427
+ ||
428
+ code == HttpStatusCode . RedirectKeepVerb
429
+ ||
430
+ code == HttpStatusCode . SeeOther
431
+ ) ;
432
+ }
433
+
434
+ internal virtual HttpResponseMessage GetResponse ( HttpClient client , HttpRequestMessage request , bool stripAuthorization )
371
435
{
372
436
if ( client == null ) { throw new ArgumentNullException ( "client" ) ; }
373
437
if ( request == null ) { throw new ArgumentNullException ( "request" ) ; }
374
438
375
439
_cancelToken = new CancellationTokenSource ( ) ;
376
- return client . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead , _cancelToken . Token ) . GetAwaiter ( ) . GetResult ( ) ;
440
+ HttpResponseMessage response = client . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead , _cancelToken . Token ) . GetAwaiter ( ) . GetResult ( ) ;
441
+
442
+ if ( stripAuthorization && IsRedirectCode ( response . StatusCode ) )
443
+ {
444
+ _cancelToken . Cancel ( ) ;
445
+ _cancelToken = null ;
446
+
447
+ // if explicit count was provided, reduce it for this redirection.
448
+ if ( WebSession . MaximumRedirection > 0 )
449
+ {
450
+ WebSession . MaximumRedirection -- ;
451
+ }
452
+ // For selected redirects that used POST, GET must be used with the
453
+ // redirected Location.
454
+ // Since GET is the default; POST only occurs when -Method POST is used.
455
+ if ( Method == WebRequestMethod . Post && IsRedirectToGet ( response . StatusCode ) )
456
+ {
457
+ // See https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.110).aspx
458
+ Method = WebRequestMethod . Get ;
459
+ }
460
+
461
+ // recreate the HttpClient with redirection enabled since the first call suppressed redirection
462
+ using ( client = GetHttpClient ( false ) )
463
+ using ( HttpRequestMessage redirectRequest = GetRequest ( response . Headers . Location , stripAuthorization : true ) )
464
+ {
465
+ FillRequestStream ( redirectRequest ) ;
466
+ _cancelToken = new CancellationTokenSource ( ) ;
467
+ response = client . SendAsync ( redirectRequest , HttpCompletionOption . ResponseHeadersRead , _cancelToken . Token ) . GetAwaiter ( ) . GetResult ( ) ;
468
+ }
469
+ }
470
+ return response ;
377
471
}
378
472
379
473
internal virtual void UpdateSession ( HttpResponseMessage response )
@@ -396,7 +490,17 @@ protected override void ProcessRecord()
396
490
ValidateParameters ( ) ;
397
491
PrepareSession ( ) ;
398
492
399
- using ( HttpClient client = GetHttpClient ( ) )
493
+ // if the request contains an authorization header and PreserveAuthorizationOnRedirect is not set,
494
+ // it needs to be stripped on the first redirect.
495
+ bool stripAuthorization = null != WebSession
496
+ &&
497
+ null != WebSession . Headers
498
+ &&
499
+ ! PreserveAuthorizationOnRedirect . IsPresent
500
+ &&
501
+ WebSession . Headers . ContainsKey ( HttpKnownHeaderNames . Authorization . ToString ( ) ) ;
502
+
503
+ using ( HttpClient client = GetHttpClient ( stripAuthorization ) )
400
504
{
401
505
int followedRelLink = 0 ;
402
506
Uri uri = Uri ;
@@ -410,7 +514,7 @@ protected override void ProcessRecord()
410
514
WriteVerbose ( linkVerboseMsg ) ;
411
515
}
412
516
413
- using ( HttpRequestMessage request = GetRequest ( uri ) )
517
+ using ( HttpRequestMessage request = GetRequest ( uri , stripAuthorization : false ) )
414
518
{
415
519
FillRequestStream ( request ) ;
416
520
try
@@ -426,7 +530,7 @@ protected override void ProcessRecord()
426
530
requestContentLength ) ;
427
531
WriteVerbose ( reqVerboseMsg ) ;
428
532
429
- HttpResponseMessage response = GetResponse ( client , request ) ;
533
+ HttpResponseMessage response = GetResponse ( client , request , stripAuthorization ) ;
430
534
431
535
string contentType = ContentHelper . GetContentType ( response ) ;
432
536
string respVerboseMsg = string . Format ( CultureInfo . CurrentCulture ,
0 commit comments