diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
index fcb1eda744b..567a98ebfb9 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
@@ -351,6 +351,12 @@ public virtual string CustomMethod
private string _custommethod;
+ ///
+ /// Gets or sets the PreserveHttpMethodOnRedirect property.
+ ///
+ [Parameter]
+ public virtual SwitchParameter PreserveHttpMethodOnRedirect { get; set; }
+
#endregion Method
#region NoProxy
@@ -509,7 +515,7 @@ protected override void ProcessRecord()
bool keepAuthorizationOnRedirect = PreserveAuthorizationOnRedirect.IsPresent
&& WebSession.Headers.ContainsKey(HttpKnownHeaderNames.Authorization);
- bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect;
+ bool handleRedirect = keepAuthorizationOnRedirect || AllowInsecureRedirect || PreserveHttpMethodOnRedirect;
using (HttpClient client = GetHttpClient(handleRedirect))
{
@@ -1239,9 +1245,8 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM
}
// For selected redirects, GET must be used with the redirected Location.
- if (currentRequest.Method == HttpMethod.Post && IsRedirectToGet(response.StatusCode))
+ if (RequestRequiresForceGet(response.StatusCode, currentRequest.Method) && !PreserveHttpMethodOnRedirect)
{
- // See https://msdn.microsoft.com/library/system.net.httpstatuscode(v=vs.110).aspx
Method = WebRequestMethod.Get;
CustomMethod = string.Empty;
}
@@ -1713,7 +1718,7 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream
private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file)
{
StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open));
-
+
// .NET does not enclose field names in quotes, however, modern browsers and curl do.
result.Headers.ContentDisposition.FileName = "\"" + file.Name + "\"";
@@ -1773,43 +1778,34 @@ private static string FormatErrorMessage(string error, string contentType)
}
// Returns true if the status code is one of the supported redirection codes.
- private static bool IsRedirectCode(HttpStatusCode code)
+ private static bool IsRedirectCode(HttpStatusCode statusCode) => statusCode switch
{
- int intCode = (int)code;
- return
- (
- (intCode >= 300 && intCode < 304) ||
- intCode == 307 ||
- intCode == 308
- );
- }
+ HttpStatusCode.Found
+ or HttpStatusCode.Moved
+ or HttpStatusCode.MultipleChoices
+ or HttpStatusCode.PermanentRedirect
+ or HttpStatusCode.SeeOther
+ or HttpStatusCode.TemporaryRedirect => true,
+ _ => false
+ };
- // Returns true if the status code is a redirection code and the action requires switching from POST to GET on redirection.
- // NOTE: Some of these status codes map to the same underlying value but spelling them out for completeness.
- private static bool IsRedirectToGet(HttpStatusCode code)
+ // Returns true if the status code is a redirection code and the action requires switching to GET on redirection.
+ // See https://learn.microsoft.com/en-us/dotnet/api/system.net.httpstatuscode
+ private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod) => statusCode switch
{
- return
- (
- code == HttpStatusCode.Found ||
- code == HttpStatusCode.Moved ||
- code == HttpStatusCode.Redirect ||
- code == HttpStatusCode.RedirectMethod ||
- code == HttpStatusCode.SeeOther ||
- code == HttpStatusCode.Ambiguous ||
- code == HttpStatusCode.MultipleChoices
- );
- }
+ HttpStatusCode.Found
+ or HttpStatusCode.Moved
+ or HttpStatusCode.MultipleChoices => requestMethod == HttpMethod.Post,
+ HttpStatusCode.SeeOther => requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head,
+ _ => false
+ };
// Returns true if the status code shows a server or client error and MaximumRetryCount > 0
- private bool ShouldRetry(HttpStatusCode code)
+ private static bool ShouldRetry(HttpStatusCode statusCode) => (int)statusCode switch
{
- int intCode = (int)code;
-
- return
- (
- (intCode == 304 || (intCode >= 400 && intCode <= 599)) && WebSession.MaximumRetryCount > 0
- );
- }
+ 304 or (>= 400 and <= 599) => true,
+ _ => false
+ };
private static HttpMethod GetHttpMethod(WebRequestMethod method) => method switch
{
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
index 3538a799b9b..2da345fcd3a 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
@@ -139,6 +139,9 @@ function ExecuteRedirectRequest {
[switch]
$PreserveAuthorizationOnRedirect,
+ [switch]
+ $PreserveHttpMethodOnRedirect,
+
[ValidateRange(0, [int]::MaxValue)]
[int]
$MaximumRedirection
@@ -153,7 +156,7 @@ function ExecuteRedirectRequest {
} elseif ($CustomMethod) {
$result.Output = Invoke-WebRequest -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -CustomMethod $CustomMethod
} else {
- $result.Output = Invoke-WebRequest -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method
+ $result.Output = Invoke-WebRequest -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -PreserveHttpMethodOnRedirect:$PreserveHttpMethodOnRedirect.IsPresent -Method $Method
}
$result.Content = $result.Output.Content | ConvertFrom-Json
} else {
@@ -162,7 +165,7 @@ function ExecuteRedirectRequest {
} elseif ($CustomMethod) {
$result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -CustomMethod $CustomMethod
} else {
- $result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method
+ $result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -PreserveHttpMethodOnRedirect:$PreserveHttpMethodOnRedirect.IsPresent -Method $Method
}
# NOTE: $result.Output should already be a PSObject (Invoke-RestMethod converts the returned json automatically)
# so simply reference $result.Output
@@ -1023,6 +1026,20 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" {
$response.Content.Method | Should -Be $redirectedMethod
}
+ It "Validates Invoke-WebRequest -PreserveHttpMethodOnRedirect keeps the authorization header redirects and do remains POST when it handles the redirect: " -TestCases $redirectTests {
+ param($redirectType)
+ $uri = Get-WebListenerUrl -Test 'Redirect' -Query @{type = $redirectType}
+ $response = ExecuteRedirectRequest -PreserveHttpMethodOnRedirect -Uri $uri -Method 'POST'
+
+ $response.Error | Should -BeNullOrEmpty
+ # ensure user-agent is present (i.e., no false positives )
+ $response.Content.Headers."User-Agent" | Should -Not -BeNullOrEmpty
+ # ensure Authorization header has been kept.
+ $response.Content.Headers."Authorization" | Should -BeExactly 'test'
+ # ensure POST doesn't change.
+ $response.Content.Method | Should -Be 'POST'
+ }
+
It "Validates Invoke-WebRequest handles responses without Location header for requests with Authorization header and redirect: " -TestCases $redirectTests {
param($redirectType, $redirectedMethod)
# Skip relative test as it is not a valid response type.
@@ -2745,6 +2762,20 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" {
$response.Content.Method | Should -Be $redirectedMethod
}
+ It "Validates Invoke-RestMethod -PreserveHttpMethodOnRedirect keeps the authorization header redirects and remains POST when it handles the redirect: " -TestCases $redirectTests {
+ param($redirectType)
+ $uri = Get-WebListenerUrl -Test 'Redirect' -Query @{type = $redirectType}
+ $response = ExecuteRedirectRequest -PreserveHttpMethodOnRedirect -Cmdlet 'Invoke-RestMethod' -Uri $uri -Method 'POST'
+
+ $response.Error | Should -BeNullOrEmpty
+ # ensure user-agent is present (i.e., no false positives )
+ $response.Content.Headers."User-Agent" | Should -Not -BeNullOrEmpty
+ # ensure Authorization header has been kept.
+ $response.Content.Headers."Authorization" | Should -BeExactly 'test'
+ # ensure POST doesn't change.
+ $response.Content.Method | Should -Be 'POST'
+ }
+
It "Validates Invoke-RestMethod handles responses without Location header for requests with Authorization header and redirect: " -TestCases $redirectTests {
param($redirectType, $redirectedMethod)
# Skip relative test as it is not a valid response type.