From 12cba32a1130d61005904e2a2cd9c8ce0221e19c Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 1 Oct 2019 16:05:08 -0700 Subject: [PATCH] Make UseStatusCodePagesWithReExecute clear Endpoints (#14531) --- .../StatusCodePagesExtensions.cs | 7 + .../test/UnitTests/ExceptionHandlerTest.cs | 106 ----------- .../UnitTests/StatusCodeMiddlewareTest.cs | 174 ++++++++++++++++++ 3 files changed, 181 insertions(+), 106 deletions(-) create mode 100644 src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs diff --git a/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesExtensions.cs b/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesExtensions.cs index 1a2707a1ed..a7d54489d9 100644 --- a/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesExtensions.cs +++ b/src/Middleware/Diagnostics/src/StatusCodePage/StatusCodePagesExtensions.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder @@ -188,6 +189,12 @@ namespace Microsoft.AspNetCore.Builder OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null, }); + // An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset + // the endpoint and route values to ensure things are re-calculated. + context.HttpContext.SetEndpoint(endpoint: null); + var routeValuesFeature = context.HttpContext.Features.Get(); + routeValuesFeature?.RouteValues?.Clear(); + context.HttpContext.Request.Path = newPath; context.HttpContext.Request.QueryString = newQueryString; try diff --git a/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs b/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs index cf48cd5235..b63eaa4a98 100644 --- a/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs +++ b/src/Middleware/Diagnostics/test/UnitTests/ExceptionHandlerTest.cs @@ -177,112 +177,6 @@ namespace Microsoft.AspNetCore.Diagnostics } } - [Fact] - public async Task Redirect_StatusPage() - { - var expectedStatusCode = 432; - var destination = "/location"; - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseStatusCodePagesWithRedirects("/errorPage?id={0}"); - - app.Map(destination, (innerAppBuilder) => - { - innerAppBuilder.Run((httpContext) => - { - httpContext.Response.StatusCode = expectedStatusCode; - return Task.FromResult(1); - }); - }); - - app.Map("/errorPage", (innerAppBuilder) => - { - innerAppBuilder.Run(async (httpContext) => - { - await httpContext.Response.WriteAsync(httpContext.Request.QueryString.Value); - }); - }); - - app.Run((context) => - { - - throw new InvalidOperationException($"Invalid input provided. {context.Request.Path}"); - }); - }); - var expectedQueryString = $"?id={expectedStatusCode}"; - var expectedUri = $"/errorPage{expectedQueryString}"; - using (var server = new TestServer(builder)) - { - - var client = server.CreateClient(); - var response = await client.GetAsync(destination); - Assert.Equal(HttpStatusCode.Found, response.StatusCode); - Assert.Equal(expectedUri, response.Headers.First(s => s.Key == "Location").Value.First()); - - response = await client.GetAsync(expectedUri); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal(expectedQueryString, content); - Assert.Equal(expectedQueryString, response.RequestMessage.RequestUri.Query); - } - } - - [Fact] - public async Task Reexecute_CanRetrieveInformationAboutOriginalRequest() - { - var expectedStatusCode = 432; - var destination = "/location"; - var builder = new WebHostBuilder() - .Configure(app => - { - app.Use(async (context, next) => - { - var beforeNext = context.Request.QueryString; - await next(); - var afterNext = context.Request.QueryString; - - Assert.Equal(beforeNext, afterNext); - }); - app.UseStatusCodePagesWithReExecute(pathFormat: "/errorPage", queryFormat: "?id={0}"); - - app.Map(destination, (innerAppBuilder) => - { - innerAppBuilder.Run((httpContext) => - { - httpContext.Response.StatusCode = expectedStatusCode; - return Task.FromResult(1); - }); - }); - - app.Map("/errorPage", (innerAppBuilder) => - { - innerAppBuilder.Run(async (httpContext) => - { - var statusCodeReExecuteFeature = httpContext.Features.Get(); - await httpContext.Response.WriteAsync( - httpContext.Request.QueryString.Value - + ", " - + statusCodeReExecuteFeature.OriginalPath - + ", " - + statusCodeReExecuteFeature.OriginalQueryString); - }); - }); - - app.Run((context) => - { - throw new InvalidOperationException("Invalid input provided."); - }); - }); - - using (var server = new TestServer(builder)) - { - var client = server.CreateClient(); - var response = await client.GetAsync(destination + "?name=James"); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal($"?id={expectedStatusCode}, /location, ?name=James", content); - } - } - [Fact] public async Task ClearsCacheHeaders_SetByReexecutionPathHandlers() { diff --git a/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs b/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs new file mode 100644 index 0000000000..c2bb236c4f --- /dev/null +++ b/src/Middleware/Diagnostics/test/UnitTests/StatusCodeMiddlewareTest.cs @@ -0,0 +1,174 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Diagnostics +{ + public class StatusCodeMiddlewareTest + { + [Fact] + public async Task Redirect_StatusPage() + { + var expectedStatusCode = 432; + var destination = "/location"; + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseStatusCodePagesWithRedirects("/errorPage?id={0}"); + + app.Map(destination, (innerAppBuilder) => + { + innerAppBuilder.Run((httpContext) => + { + httpContext.Response.StatusCode = expectedStatusCode; + return Task.FromResult(1); + }); + }); + + app.Map("/errorPage", (innerAppBuilder) => + { + innerAppBuilder.Run(async (httpContext) => + { + await httpContext.Response.WriteAsync(httpContext.Request.QueryString.Value); + }); + }); + + app.Run((context) => + { + throw new InvalidOperationException($"Invalid input provided. {context.Request.Path}"); + }); + }); + var expectedQueryString = $"?id={expectedStatusCode}"; + var expectedUri = $"/errorPage{expectedQueryString}"; + using var server = new TestServer(builder); + var client = server.CreateClient(); + var response = await client.GetAsync(destination); + Assert.Equal(HttpStatusCode.Found, response.StatusCode); + Assert.Equal(expectedUri, response.Headers.First(s => s.Key == "Location").Value.First()); + + response = await client.GetAsync(expectedUri); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedQueryString, content); + Assert.Equal(expectedQueryString, response.RequestMessage.RequestUri.Query); + } + + [Fact] + public async Task Reexecute_CanRetrieveInformationAboutOriginalRequest() + { + var expectedStatusCode = 432; + var destination = "/location"; + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use(async (context, next) => + { + var beforeNext = context.Request.QueryString; + await next(); + var afterNext = context.Request.QueryString; + + Assert.Equal(beforeNext, afterNext); + }); + app.UseStatusCodePagesWithReExecute(pathFormat: "/errorPage", queryFormat: "?id={0}"); + + app.Map(destination, (innerAppBuilder) => + { + innerAppBuilder.Run((httpContext) => + { + httpContext.Response.StatusCode = expectedStatusCode; + return Task.FromResult(1); + }); + }); + + app.Map("/errorPage", (innerAppBuilder) => + { + innerAppBuilder.Run(async (httpContext) => + { + var statusCodeReExecuteFeature = httpContext.Features.Get(); + await httpContext.Response.WriteAsync( + httpContext.Request.QueryString.Value + + ", " + + statusCodeReExecuteFeature.OriginalPath + + ", " + + statusCodeReExecuteFeature.OriginalQueryString); + }); + }); + + app.Run((context) => + { + throw new InvalidOperationException("Invalid input provided."); + }); + }); + + using var server = new TestServer(builder); + var client = server.CreateClient(); + var response = await client.GetAsync(destination + "?name=James"); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal($"?id={expectedStatusCode}, /location, ?name=James", content); + } + + [Fact] + public async Task Reexecute_ClearsEndpointAndRouteData() + { + var expectedStatusCode = 432; + var destination = "/location"; + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseStatusCodePagesWithReExecute(pathFormat: "/errorPage", queryFormat: "?id={0}"); + + app.Use((context, next) => + { + Assert.Empty(context.Request.RouteValues); + Assert.Null(context.GetEndpoint()); + return next(); + }); + + app.Map(destination, (innerAppBuilder) => + { + innerAppBuilder.Run((httpContext) => + { + httpContext.SetEndpoint(new Endpoint((_) => Task.CompletedTask, new EndpointMetadataCollection(), "Test")); + httpContext.Request.RouteValues["John"] = "Doe"; + httpContext.Response.StatusCode = expectedStatusCode; + return Task.CompletedTask; + }); + }); + + app.Map("/errorPage", (innerAppBuilder) => + { + innerAppBuilder.Run(async (httpContext) => + { + var statusCodeReExecuteFeature = httpContext.Features.Get(); + await httpContext.Response.WriteAsync( + httpContext.Request.QueryString.Value + + ", " + + statusCodeReExecuteFeature.OriginalPath + + ", " + + statusCodeReExecuteFeature.OriginalQueryString); + }); + }); + + app.Run((context) => + { + throw new InvalidOperationException("Invalid input provided."); + }); + }); + + using var server = new TestServer(builder); + var client = server.CreateClient(); + var response = await client.GetAsync(destination + "?name=James"); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal($"?id={expectedStatusCode}, /location, ?name=James", content); + } + } +}