diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs index 261b4c98b7e..4268bc63a98 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/ContentHelper.CoreClr.cs @@ -7,6 +7,7 @@ using System.Text; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; namespace Microsoft.PowerShell.Commands { @@ -37,11 +38,30 @@ internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) raw.AppendFormat("{0} {1} {2}", protocol, statusCode, statusDescription); raw.AppendLine(); } + + HttpHeaders[] headerCollections = + { + response.Headers, + response.Content == null ? null : response.Content.Headers + }; - foreach (var entry in response.Headers) + foreach (var headerCollection in headerCollections) { - raw.AppendFormat("{0}: {1}", entry.Key, entry.Value.FirstOrDefault()); - raw.AppendLine(); + if (headerCollection == null) + { + continue; + } + foreach (var header in headerCollection) + { + // Headers may have multiple entries with different values + foreach (var headerValue in header.Value) + { + raw.Append(header.Key); + raw.Append(": "); + raw.Append(headerValue); + raw.AppendLine(); + } + } } raw.AppendLine(); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs index 79fc400af7f..9e4b52bfd0f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebResponseObject.CoreClr.cs @@ -36,6 +36,13 @@ public Dictionary> Headers { headers[entry.Key] = entry.Value; } + if (BaseResponse.Content != null) + { + foreach (var entry in BaseResponse.Content.Headers) + { + headers[entry.Key] = entry.Value; + } + } return headers; } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 638f94f1dd0..fc65586af6e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1070,6 +1070,45 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { #endregion charset encoding tests + #region Content Header Inclusion + It "Verifies Invoke-WebRequest includes Content headers in Headers property" { + $uri = "http://localhost:8080/PowerShell?test=response&contenttype=text/plain&output=OK" + $command = "Invoke-WebRequest -Uri '$uri'" + $result = ExecuteWebCommand -command $command + ValidateResponse $result + + $result.Output.Headers.'Content-Type' | Should Be 'text/plain' + $result.Output.Headers.'Content-Length' | Should Be 2 + } + + It "Verifies Invoke-WebRequest includes Content headers in RawContent property" { + $uri = "http://localhost:8080/PowerShell?test=response&contenttype=text/plain&output=OK" + $command = "Invoke-WebRequest -Uri '$uri'" + $result = ExecuteWebCommand -command $command + ValidateResponse $result + + $result.Output.RawContent | Should Match ([regex]::Escape('Content-Type: text/plain')) + $result.Output.RawContent | Should Match ([regex]::Escape('Content-Length: 2')) + } + + It "Verifies Invoke-WebRequest Supports Multiple response headers with same name" { + $headers = @{ + 'X-Fake-Header' = 'testvalue01','testvalue02' + } | ConvertTo-Json -Compress + $uri = "http://localhost:8080/PowerShell?test=response&contenttype=text/plain&output=OK&headers=$headers" + $command = "Invoke-WebRequest -Uri '$uri'" + $result = ExecuteWebCommand -command $command + ValidateResponse $result + + $result.Output.Headers.'X-Fake-Header'.Count | Should Be 2 + $result.Output.Headers.'X-Fake-Header'.Contains('testvalue01') | Should Be $True + $result.Output.Headers.'X-Fake-Header'.Contains('testvalue02') | Should Be $True + $result.Output.RawContent | Should Match ([regex]::Escape('X-Fake-Header: testvalue01')) + $result.Output.RawContent | Should Match ([regex]::Escape('X-Fake-Header: testvalue02')) + } + + #endregion Content Header Inclusion + 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 8fc048c8cc7..b682235617a 100644 --- a/test/tools/Modules/HttpListener/HttpListener.psm1 +++ b/test/tools/Modules/HttpListener/HttpListener.psm1 @@ -133,6 +133,20 @@ Function Start-HTTPListener { $statusCode = $queryItems["statuscode"] $contentType = $queryItems["contenttype"] $output = $queryItems["output"] + + # Pass a JSON collection to the 'headers' field + # /PowerShell?test=response&headers={"Pragma":"no-cache","X-Fake-Header":["testvalue01","testvalue02"]} + # In PowerShell: + # $headers = @{Pragma='no-cache';'X-Fake-Header'='testvalue01','testvalue02'} | ConvertTo-Json -Compress + # $uri = "http://localhost:8080/PowerShell?test=response&headers=$headers" + if ($queryItems['headers']) + { + $headerCollection = $queryItems['headers'] | ConvertFrom-Json + foreach ($header in $headerCollection.psobject.Properties.name) + { + $outputHeader.add($header,$headerCollection.$header) + } + } } # Echo the request as the output. @@ -284,7 +298,10 @@ Function Start-HTTPListener { $response.Headers.Clear() foreach ($header in $outputHeader.Keys) { - $response.Headers.Add($header, $outputHeader[$header]) + foreach ($headerValue in $outputHeader.$header) + { + $response.Headers.Add($header, $headerValue) + } } if ($null -ne $output)