[Fixes #8021] Copy the request headers before sending the request on the RedirectHandler

If another handler modifies the request headers the modified headers get
applied on subsequent requests, which is not correct. This change copies
the headers before sending the request and uses the original headers for
the redirect request instead of the potentially modified ones.
This commit is contained in:
Javier Calvarro Nelson 2018-08-21 17:01:20 -07:00
parent 8eea0ad44c
commit dfb579d45c
3 changed files with 97 additions and 18 deletions

View File

@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
@ -49,13 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var remainingRedirects = MaxRedirects;
var redirectRequest = new HttpRequestMessage();
var originalRequestContent = HasBody(request) ? await DuplicateRequestContent(request) : null;
CopyRequestHeaders(request.Headers, redirectRequest.Headers);
var response = await base.SendAsync(request, cancellationToken);
while (IsRedirect(response) && remainingRedirects >= 0)
{
remainingRedirects--;
var redirectRequest = GetRedirectRequest(response, originalRequestContent);
UpdateRedirectRequest(response, redirectRequest, originalRequestContent);
originalRequestContent = HasBody(redirectRequest) ? await DuplicateRequestContent(redirectRequest) : null;
response = await base.SendAsync(redirectRequest, cancellationToken);
}
@ -95,6 +97,16 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers
}
}
private static void CopyRequestHeaders(
HttpRequestHeaders originalRequestHeaders,
HttpRequestHeaders newRequestHeaders)
{
foreach (var header in originalRequestHeaders)
{
newRequestHeaders.Add(header.Key, header.Value);
}
}
private static async Task<(Stream originalBody, Stream copy)> CopyBody(HttpRequestMessage request)
{
var originalBody = await request.Content.ReadAsStreamAsync();
@ -116,8 +128,9 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers
return (originalBody, bodyCopy);
}
private static HttpRequestMessage GetRedirectRequest(
private static void UpdateRedirectRequest(
HttpResponseMessage response,
HttpRequestMessage redirect,
HttpContent originalContent)
{
var location = response.Headers.Location;
@ -128,34 +141,31 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers
location);
}
var redirect = !ShouldKeepVerb(response) ?
new HttpRequestMessage(HttpMethod.Get, location) :
new HttpRequestMessage(response.RequestMessage.Method, location)
{
Content = originalContent
};
foreach (var header in response.RequestMessage.Headers)
redirect.RequestUri = location;
if (!ShouldKeepVerb(response))
{
redirect.Headers.Add(header.Key, header.Value);
redirect.Method = HttpMethod.Get;
}
else
{
redirect.Method = response.RequestMessage.Method;
redirect.Content = originalContent;
}
foreach (var property in response.RequestMessage.Properties)
{
redirect.Properties.Add(property.Key, property.Value);
}
return redirect;
}
private static bool ShouldKeepVerb(HttpResponseMessage response) =>
response.StatusCode == HttpStatusCode.RedirectKeepVerb ||
(int)response.StatusCode == 308;
private bool IsRedirect(HttpResponseMessage response) =>
private bool IsRedirect(HttpResponseMessage response) =>
response.StatusCode == HttpStatusCode.MovedPermanently ||
response.StatusCode == HttpStatusCode.Redirect ||
response.StatusCode == HttpStatusCode.RedirectKeepVerb ||
(int)response.StatusCode == 308;
}
}
}

View File

@ -2,11 +2,13 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using BasicWebSite;
using BasicWebSite.Controllers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Mvc.Testing.Handlers;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
@ -17,13 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public TestingInfrastructureTests(WebApplicationFactory<BasicWebSite.Startup> fixture)
{
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
Client = factory.CreateClient();
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
Client = Factory.CreateClient();
}
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
builder.ConfigureTestServices(s => s.AddSingleton<TestService, OverridenService>());
public WebApplicationFactory<Startup> Factory { get; }
public HttpClient Client { get; }
[Fact]
@ -57,6 +60,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(5, handlerResponse.Body);
}
[Fact]
public async Task TestingInfrastructure_RedirectHandlerUsesOriginalRequestHeaders()
{
// Act
var request = new HttpRequestMessage(HttpMethod.Get, "Testing/RedirectHandler/Headers");
var client = Factory.CreateDefaultClient(
new RedirectHandler(), new TestHandler());
var response = await client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var modifiedHeaderWasSent = await response.Content.ReadAsStringAsync();
Assert.Equal("false", modifiedHeaderWasSent);
}
[Fact]
public async Task TestingInfrastructure_PostRedirectGetWorksWithCookies()
{
@ -93,5 +112,29 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Message = "Test";
}
}
private class TestHandler : DelegatingHandler
{
public TestHandler()
{
}
public TestHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
public bool HeaderAdded { get; set; }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!HeaderAdded)
{
request.Headers.Add("X-Added-Header", "true");
HeaderAdded = true;
}
return base.SendAsync(request, cancellationToken);
}
}
}
}

View File

@ -37,6 +37,32 @@ namespace BasicWebSite.Controllers
return Ok(new RedirectHandlerResponse { Url = value, Body = number.Value });
}
[HttpGet("Testing/RedirectHandler/Headers")]
public IActionResult RedirectHandlerHeaders()
{
if (!Request.Headers.TryGetValue("X-Added-Header", out var value))
{
return Content("No header present");
}
else
{
return RedirectToAction(nameof(RedirectHandlerHeadersRedirect));
}
}
[HttpGet("Testing/RedirectHandler/Headers/Redirect")]
public IActionResult RedirectHandlerHeadersRedirect()
{
if (Request.Headers.TryGetValue("X-Added-Header", out var value))
{
return Content("true");
}
else
{
return Content("false");
}
}
[HttpGet("Testing/AntiforgerySimulator/{value}")]
public IActionResult AntiforgerySimulator([FromRoute]int value)
{