From aed7203e9c80de934875953e5b9062e35e1825e6 Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons" Date: Sat, 21 Dec 2019 09:15:33 -0800 Subject: [PATCH 1/3] Fix for issue 11396 regarding blank ContentType for web request with no body. Regarding `Invoke-WebRequest`: A change was made to PowerShell Core with commit `2285ece613` that changes the way web request headers are handled when the web request has no body. Previously, they would be ignored. Now, they are processed anyway, including the `ContentType` header, which makes no sense when there's no body. Now, the headers are processed anyway, and if there's a `ContentType` header with an empty string value, it will fail. When a customer tries to pass in a blank (i.e., `$null`) `ContentType` to a function that takes a `string` because they have no body, it gets treated as an empty string, not `$null`. This works fin in Windows PowerShell (PS5 and earlier) and PowerShell Core 6, because their code bases don't have commit `2285ece613`. When it fails, they get a message saying that they can avoid this by passing `-SkipHeaderValidation`, but this flag isn't available in Windows PowerShell, and using it would require a crude hard-coded version check to see if you're on Windows PowerShell or PowerShell Core. This change merely skips adding headers to the request if the header value is nothing (null or whitespace). **Question**: Should we only do this *skip* for the `ContentType` header? https://github.com/PowerShell/PowerShell/issues/11396 --- .../Common/WebRequestPSCmdlet.Common.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) 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 ddac58fe57d..4c5d3e82275 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 @@ -1260,21 +1260,24 @@ internal virtual void FillRequestStream(HttpRequestMessage request) foreach (var entry in WebSession.ContentHeaders) { - if (SkipHeaderValidation) - { - request.Content.Headers.TryAddWithoutValidation(entry.Key, entry.Value); - } - else + if (!string.IsNullOrWhiteSpace(entry.Value)) { - try + if (SkipHeaderValidation) { - request.Content.Headers.Add(entry.Key, entry.Value); + request.Content.Headers.TryAddWithoutValidation(entry.Key, entry.Value); } - catch (FormatException ex) + else { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); - ErrorRecord er = new ErrorRecord(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); - ThrowTerminatingError(er); + try + { + request.Content.Headers.Add(entry.Key, entry.Value); + } + catch (FormatException ex) + { + var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); + ErrorRecord er = new ErrorRecord(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); + ThrowTerminatingError(er); + } } } } From 1ffa16f8139c81b5738eae7eca4d82177e8be766 Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons" Date: Fri, 27 Dec 2019 23:10:41 -0800 Subject: [PATCH 2/3] Add a test for blank ContentType in Invoke-WebRequest --- .../WebCmdlets.Tests.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index d4ab2ff5009..c535c909e8e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -447,6 +447,18 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $jsonContent.headers.Host | Should -Be $Uri.Authority } + It "Invoke-WebRequest blank ContentType" { + $uri = Get-WebListenerUrl -Test 'Get' + $command = "Invoke-WebRequest -Uri '$uri' -ContentType ''" + + $result = ExecuteWebCommand -command $command + ValidateResponse -response $result + + # Validate response content + $jsonContent = $result.Output.Content | ConvertFrom-Json + $jsonContent.headers.Host | Should -Be $Uri.Authority + } + It "Validate Invoke-WebRequest -DisableKeepAlive" { # Operation options $uri = Get-WebListenerUrl -Test 'Get' From 31c63d8e6ecc025fef6cd862668424a9182c7ec6 Mon Sep 17 00:00:00 2001 From: "Michael J. Lyons" Date: Sat, 28 Dec 2019 12:18:02 -0800 Subject: [PATCH 3/3] Add a test for blank ContentType in Invoke-RestMethod --- .../WebCmdlets.Tests.ps1 | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index c535c909e8e..2e234ea5940 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -447,16 +447,14 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $jsonContent.headers.Host | Should -Be $Uri.Authority } - It "Invoke-WebRequest blank ContentType" { + It "Invoke-WebRequest with blank ContentType succeeds" { $uri = Get-WebListenerUrl -Test 'Get' $command = "Invoke-WebRequest -Uri '$uri' -ContentType ''" $result = ExecuteWebCommand -command $command - ValidateResponse -response $result - # Validate response content - $jsonContent = $result.Output.Content | ConvertFrom-Json - $jsonContent.headers.Host | Should -Be $Uri.Authority + # Validate response + ValidateResponse -response $result } It "Validate Invoke-WebRequest -DisableKeepAlive" { @@ -2007,6 +2005,16 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Output.headers.'User-Agent' | Should -MatchExactly '.*\(Windows NT \d+\.\d*;.*\) PowerShell\/\d+\.\d+\.\d+.*' } + It "Invoke-RestMethod with blank ContentType succeeds" { + $uri = Get-WebListenerUrl -Test 'Get' + $command = "Invoke-RestMethod -Uri '$uri' -ContentType ''" + + $result = ExecuteWebCommand -command $command + + # Validate response + $result.Error | Should -BeNullOrEmpty + } + It "Invoke-RestMethod returns headers dictionary" { $uri = Get-WebListenerUrl -Test 'Get' $command = "Invoke-RestMethod -Uri '$uri'"