diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs index 24aa3cb..cef46be 100644 --- a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs +++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgery.cs @@ -67,9 +67,12 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal } } - // Explicitly set the cache headers to 'no-cache'. This could override any user set value but this is fine - // as a response with antiforgery token must never be cached. - SetDoNotCacheHeaders(httpContext); + if (!httpContext.Response.HasStarted) + { + // Explicitly set the cache headers to 'no-cache'. This could override any user set value but this is fine + // as a response with antiforgery token must never be cached. + SetDoNotCacheHeaders(httpContext); + } return tokenSet; } @@ -247,7 +250,10 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal _logger.ReusedCookieToken(); } - SetDoNotCacheHeaders(httpContext); + if (!httpContext.Response.HasStarted) + { + SetDoNotCacheHeaders(httpContext); + } } private void SaveCookieTokenAndHeader(HttpContext httpContext, string cookieToken) diff --git a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs index 4eebaad..faf895d 100644 --- a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs +++ b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -1137,6 +1138,47 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal context.TokenSerializer.Verify(s => s.Deserialize(null), Times.Never); } + [Fact] + public void SetCookieTokenAndHeader_DoesNotModifyHeadersAfterResponseHasStarted() + { + // Arrange + var antiforgeryFeature = new AntiforgeryFeature + { + HaveDeserializedCookieToken = false, + HaveGeneratedNewCookieToken = false, + HaveStoredNewCookieToken = true, + NewCookieToken = new AntiforgeryToken(), + NewCookieTokenString = "serialized-cookie-token-from-context", + NewRequestToken = new AntiforgeryToken(), + NewRequestTokenString = "serialized-form-token-from-context", + }; + var context = CreateMockContext( + new AntiforgeryOptions(), + useOldCookie: false, + isOldCookieValid: false, + antiforgeryFeature: antiforgeryFeature); + var testTokenSet = new TestTokenSet + { + OldCookieTokenString = null + }; + + var nullTokenStore = GetTokenStore(context.HttpContext, testTokenSet, false); + var antiforgery = GetAntiforgery( + context.HttpContext, + tokenGenerator: context.TokenGenerator.Object, + tokenStore: nullTokenStore.Object); + + TestResponseFeature testResponse = new TestResponseFeature(); + context.HttpContext.Features.Set(testResponse); + context.HttpContext.Response.Headers["Cache-Control"] = "public"; + testResponse.StartResponse(); + + // Act + antiforgery.SetCookieTokenAndHeader(context.HttpContext); + + Assert.Equal("public", context.HttpContext.Response.Headers["Cache-Control"]); + } + [Fact] public void GetAndStoreTokens_DoesNotLogWarning_IfNoExistingCacheHeadersPresent() { @@ -1435,5 +1477,21 @@ namespace Microsoft.AspNetCore.Antiforgery.Internal { public AntiforgeryOptions Value { get; set; } = new AntiforgeryOptions(); } + + private class TestResponseFeature : HttpResponseFeature + { + private bool _hasStarted = false; + + public override bool HasStarted { get => _hasStarted; } + + public TestResponseFeature() + { + } + + public void StartResponse() + { + _hasStarted = true; + } + } } }