Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b4467aa
Add PersistHTTPMethod parameter
CarloToso Jan 5, 2023
720ece8
PersistHTTPMethod handle redirect
CarloToso Jan 5, 2023
20520e6
PersistHTTPMethod manual redirect
CarloToso Jan 5, 2023
1335810
replace IsRedirectToGet with RequestRequiresForceGet from dotnet
CarloToso Jan 5, 2023
a36718c
Update IsRedirectCode with code from RedirectHandler.cs
CarloToso Jan 5, 2023
9499548
Add PersistHTTPMethod
CarloToso Jan 5, 2023
689455b
Fix error
CarloToso Jan 6, 2023
25ee798
fix test error (pasted else in the wrong place)
CarloToso Jan 6, 2023
5278cf7
Convert to SwitchParameter and PreserveHTTPMethodOnRedirect
CarloToso Jan 6, 2023
127d94c
Follow suggestions
CarloToso Jan 6, 2023
575e076
join if
CarloToso Jan 7, 2023
b7e889a
Should Retry remove maximumretrycount add switch
CarloToso Jan 7, 2023
c9b5003
add static
CarloToso Jan 7, 2023
065aa1f
RequestRequiresForceGet WebRequestMethod --> HttpMethod
CarloToso Jan 8, 2023
a3438f6
switch expressions
CarloToso Jan 9, 2023
3b68566
switch expressions alphabetical order
CarloToso Jan 9, 2023
b7d24f2
switch expressions remove last ,
CarloToso Jan 9, 2023
341a0a9
Merge branch 'master' into WebCmdlets-persist-HTTP-method
CarloToso Jan 13, 2023
9ffef1e
remove spaces
CarloToso Jan 13, 2023
ef18722
handleRedirect = PreserveHTTPMethodOnRedirect
CarloToso Jan 13, 2023
3a29cdc
remove spaces
CarloToso Jan 13, 2023
c4533ec
HTTP -> Http
CarloToso Jan 13, 2023
a9a67de
add tests
CarloToso Jan 13, 2023
495ad87
add PreserveHttpMethodOnRedirect to ExecuteRedirectRequest
CarloToso Jan 13, 2023
c2000b7
fix tests
CarloToso Jan 13, 2023
809cb3f
reverted
CarloToso Jan 13, 2023
bad7544
Merge branch 'master' into WebCmdlets-persist-HTTP-method
CarloToso Feb 7, 2023
a52207d
Merge branch 'master' into WebCmdlets-persist-HTTP-method
CarloToso Feb 8, 2023
c8aae98
merge
CarloToso Feb 10, 2023
1cedff2
Merge branch 'master' into WebCmdlets-persist-HTTP-method
CarloToso Feb 10, 2023
ffa89b3
Merge branch 'master' into WebCmdlets-persist-HTTP-method
CarloToso Feb 13, 2023
c23befb
Merge branch 'PowerShell:master' into WebCmdlets-persist-HTTP-method
CarloToso Feb 14, 2023
c91c8f3
moved or
CarloToso Feb 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,12 @@ public virtual string CustomMethod

private string _custommethod;

/// <summary>
/// Gets or sets the PreserveHttpMethodOnRedirect property.
/// </summary>
[Parameter]
public virtual SwitchParameter PreserveHttpMethodOnRedirect { get; set; }

#endregion Method

#region NoProxy
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 + "\"";

Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ function ExecuteRedirectRequest {
[switch]
$PreserveAuthorizationOnRedirect,

[switch]
$PreserveHttpMethodOnRedirect,

[ValidateRange(0, [int]::MaxValue)]
[int]
$MaximumRedirection
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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: <redirectType>" -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: <redirectType>" -TestCases $redirectTests {
param($redirectType, $redirectedMethod)
# Skip relative test as it is not a valid response type.
Expand Down Expand Up @@ -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: <redirectType>" -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: <redirectType>" -TestCases $redirectTests {
param($redirectType, $redirectedMethod)
# Skip relative test as it is not a valid response type.
Expand Down