From 7f5c3aa08c4b91765d5cd906c2c1641338d65af1 Mon Sep 17 00:00:00 2001 From: dantraMSFT Date: Thu, 22 Jun 2017 15:18:31 -0700 Subject: [PATCH 1/2] Add -SkipHeaderValidation switch to Invoke-WebRequest and Invoke-RestMethod to support adding headers without validating the header value. --- .../CoreCLR/WebRequestPSCmdlet.CoreClr.cs | 25 ++++- .../WebCmdlets.Tests.ps1 | 101 ++++++++++++++++++ .../Modules/HttpListener/HttpListener.psm1 | 7 ++ 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs index 23d35548774..bbe96fa6641 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs @@ -58,12 +58,21 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet /// CoreCLR (HTTPClient) does not have this behavior so web requests that work on /// PowerShell/FullCLR can fail with PowerShell/CoreCLR. To provide compatibility, /// we'll detect requests with an Authorization header and automatically strip - /// the header when the first redirect occurs. This switch turns off this logic for + /// the header when the first redirect occurs. This switch turns off this logic for /// edge cases where the authorization header needs to be preserved across redirects. /// [Parameter] public virtual SwitchParameter PreserveAuthorizationOnRedirect { get; set; } + /// + /// gets or sets the SkipHeaderValidation property + /// + /// + /// This property adds headers to the request's header collection without validation. + /// + [Parameter] + public virtual SwitchParameter SkipHeaderValidation { get; set; } + #region Abstract Methods /// @@ -240,14 +249,22 @@ internal virtual HttpRequestMessage GetRequest(Uri uri, bool stripAuthorization) } else { - if (stripAuthorization - && + if (stripAuthorization + && String.Equals(entry.Key, HttpKnownHeaderNames.Authorization.ToString(), StringComparison.OrdinalIgnoreCase) ) { continue; } - request.Headers.Add(entry.Key, entry.Value); + + if (SkipHeaderValidation) + { + request.Headers.TryAddWithoutValidation(entry.Key, entry.Value); + } + else + { + request.Headers.Add(entry.Key, entry.Value); + } } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 92108dc31d2..2002f317458 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -184,6 +184,49 @@ function ExecuteRedirectRequest return $result } +# This function calls either Invoke-WebRequest or Invoke-RestMethod with the given uri +# using the custum headers and the optional SkipHeaderValidation switch. +function ExecuteRequestWithCustomHeaders +{ + param ( + [Parameter(Mandatory)] + [string] + $Uri, + + [ValidateSet('Invoke-WebRequest', 'Invoke-RestMethod')] + [string] $Cmdlet = 'Invoke-WebRequest', + + [Parameter(Mandatory)] + [ValidateNotNull()] + [Hashtable] $Headers, + + [switch] $SkipHeaderValidation + ) + $result = [PSObject]@{Output = $null; Error = $null; Content = $null} + + try + { + if ($Cmdlet -eq 'Invoke-WebRequest') + { + $result.Output = Invoke-WebRequest -Uri $Uri -TimeoutSec 5 -Headers $Headers -SkipHeaderValidation:$SkipHeaderValidation.IsPresent + $result.Content = $result.Output.Content | ConvertFrom-Json + } + else + { + $result.Output = Invoke-RestMethod -Uri $Uri -TimeoutSec 5 -Headers $Headers -SkipHeaderValidation:$SkipHeaderValidation.IsPresent + # NOTE: $result.Output should already be a PSObject (Invoke-RestMethod converts the returned json automatically) + # so simply reference $result.Output + $result.Content = $result.Output + } + } + catch + { + $result.Error = $_ + } + + return $result +} + <# Defines the list of redirect codes to test as well as the expected Method when the redirection is handled. @@ -662,6 +705,35 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { #endregion Redirect tests + #region SkipHeaderVerification Tests + + It "Verifies Invoke-WebRequest default header handling with no errors" { + $headers = @{"If-Match" = "*"} + $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8080/PowerShell?test=echo" -headers $headers + + $response.Error | Should BeNullOrEmpty + $response.Content.Headers -contains "If-Match" | Should Be $true + } + + It "Verifies Invoke-WebRequest default header handling reports an error is returned for an invalid If-Match header value" { + $headers = @{"If-Match" = "12345"} + $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8080/PowerShell?test=echo" -headers $headers + + $response.Error | Should Not BeNullOrEmpty + $response.Error.FullyQualifiedErrorId | Should Be "System.FormatException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand" + $response.Error.Exception.Message | Should Be "The format of value '12345' is invalid." + } + + It "Verifies Invoke-WebRequest header handling does not report an error when using -SkipHeaderValidation" { + $headers = @{"If-Match" = "12345"} + $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8080/PowerShell?test=echo" -headers $headers -SkipHeaderValidation + + $response.Error | Should BeNullOrEmpty + $response.Content.Headers -contains "If-Match" | Should Be $true + } + + #endregion SkipHeaderVerification Tests + BeforeEach { if ($env:http_proxy) { $savedHttpProxy = $env:http_proxy @@ -1124,6 +1196,35 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { #endregion Redirect tests + #region SkipHeaderVerification tests + + It "Verifies Invoke-RestMethod default header handling with no errors" { + $headers = @{"If-Match" = "*"} + $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8081/PowerShell?test=echo" -headers $headers -Cmdlet "Invoke-RestMethod" + + $response.Error | Should BeNullOrEmpty + $response.Content.Headers -contains "If-Match" | Should Be $true + } + + It "Verifies Invoke-RestMethod default header handling reports an error is returned for an invalid If-Match header value" { + $headers = @{"If-Match" = "12345"} + $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8081/PowerShell?test=echo" -headers $headers -Cmdlet "Invoke-RestMethod" + + $response.Error | Should Not BeNullOrEmpty + $response.Error.FullyQualifiedErrorId | Should Be "System.FormatException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand" + $response.Error.Exception.Message | Should Be "The format of value '12345' is invalid." + } + + It "Verifies Invoke-RestMethod header handling does not report an error when using -SkipHeaderValidation" { + $headers = @{"If-Match" = "12345"} + $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8081/PowerShell?test=echo" -headers $headers -SkipHeaderValidation -Cmdlet "Invoke-RestMethod" + + $response.Error | Should BeNullOrEmpty + $response.Content.Headers -contains "If-Match" | Should Be $true + } + + #endregion SkipHeaderVerification tests + BeforeEach { if ($env:http_proxy) { $savedHttpProxy = $env:http_proxy diff --git a/test/tools/Modules/HttpListener/HttpListener.psm1 b/test/tools/Modules/HttpListener/HttpListener.psm1 index e38afff1385..358179d37bf 100644 --- a/test/tools/Modules/HttpListener/HttpListener.psm1 +++ b/test/tools/Modules/HttpListener/HttpListener.psm1 @@ -134,6 +134,13 @@ Function Start-HTTPListener { $contentType = $queryItems["contenttype"] $output = $queryItems["output"] } + + # Echo the request as the output. + "echo" + { + Write-Verbose -Message "Echo request" + $output = $request | ConvertTo-Json -Depth 6 + } <# This test provides support for multiple redirection types as well as a custom From 5603a731b2819b0fede8a39de0b2c0b846afbae4 Mon Sep 17 00:00:00 2001 From: Dan Travison Date: Fri, 23 Jun 2017 13:21:04 -0700 Subject: [PATCH 2/2] Fix whitespace --- .../WebCmdlets.Tests.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 2002f317458..4f01f9790b3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -211,13 +211,13 @@ function ExecuteRequestWithCustomHeaders $result.Output = Invoke-WebRequest -Uri $Uri -TimeoutSec 5 -Headers $Headers -SkipHeaderValidation:$SkipHeaderValidation.IsPresent $result.Content = $result.Output.Content | ConvertFrom-Json } - else - { + else + { $result.Output = Invoke-RestMethod -Uri $Uri -TimeoutSec 5 -Headers $Headers -SkipHeaderValidation:$SkipHeaderValidation.IsPresent # NOTE: $result.Output should already be a PSObject (Invoke-RestMethod converts the returned json automatically) # so simply reference $result.Output $result.Content = $result.Output - } + } } catch { @@ -710,7 +710,7 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { It "Verifies Invoke-WebRequest default header handling with no errors" { $headers = @{"If-Match" = "*"} $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8080/PowerShell?test=echo" -headers $headers - + $response.Error | Should BeNullOrEmpty $response.Content.Headers -contains "If-Match" | Should Be $true } @@ -730,9 +730,9 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { $response.Error | Should BeNullOrEmpty $response.Content.Headers -contains "If-Match" | Should Be $true - } + } - #endregion SkipHeaderVerification Tests + #endregion SkipHeaderVerification Tests BeforeEach { if ($env:http_proxy) { @@ -1201,7 +1201,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { It "Verifies Invoke-RestMethod default header handling with no errors" { $headers = @{"If-Match" = "*"} $response = ExecuteRequestWithCustomHeaders -Uri "http://localhost:8081/PowerShell?test=echo" -headers $headers -Cmdlet "Invoke-RestMethod" - + $response.Error | Should BeNullOrEmpty $response.Content.Headers -contains "If-Match" | Should Be $true } @@ -1223,7 +1223,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { $response.Content.Headers -contains "If-Match" | Should Be $true } - #endregion SkipHeaderVerification tests + #endregion SkipHeaderVerification tests BeforeEach { if ($env:http_proxy) {