diff --git a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs index cd20eea..17c2e14 100644 --- a/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs +++ b/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenStore.cs @@ -44,8 +44,16 @@ public async Task GetRequestTokensAsync(HttpContext httpCon var cookieToken = httpContext.Request.Cookies[_options.CookieName]; + // We want to delay reading the form as much as possible, for example in case of large file uploads, + // request token could be part of the header. StringValues requestToken; - if (httpContext.Request.HasFormContentType) + if (_options.HeaderName != null) + { + requestToken = httpContext.Request.Headers[_options.HeaderName]; + } + + // Fall back to reading form instead + if (requestToken.Count == 0 && httpContext.Request.HasFormContentType) { // Check the content-type before accessing the form collection to make sure // we report errors gracefully. @@ -53,12 +61,6 @@ public async Task GetRequestTokensAsync(HttpContext httpCon requestToken = form[_options.FormFieldName]; } - // Fall back to header if the form value was not provided. - if (requestToken.Count == 0 && _options.HeaderName != null) - { - requestToken = httpContext.Request.Headers[_options.HeaderName]; - } - return new AntiforgeryTokenSet(requestToken, cookieToken, _options.FormFieldName, _options.HeaderName); } diff --git a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs index 2b1f615..09c2758 100644 --- a/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs +++ b/test/Microsoft.AspNetCore.Antiforgery.Test/Internal/DefaultAntiforgeryTokenStoreTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; @@ -99,40 +100,44 @@ public async Task GetRequestTokens_CookieIsEmpty_ReturnsNullTokens() } [Fact] - public async Task GetRequestTokens_NonFormContentType_HeaderDisabled_ReturnsNullToken() + public async Task GetRequestTokens_HeaderTokenTakensPriority_OverFormToken() { // Arrange var httpContext = GetHttpContext("cookie-name", "cookie-value"); - httpContext.Request.ContentType = "application/json"; - - // Will not be accessed - httpContext.Request.Form = null; + httpContext.Request.ContentType = "application/x-www-form-urlencoded"; + httpContext.Request.Form = new FormCollection(new Dictionary + { + { "form-field-name", "form-value" }, + }); // header value has priority. + httpContext.Request.Headers.Add("header-name", "header-value"); var options = new AntiforgeryOptions() { CookieName = "cookie-name", FormFieldName = "form-field-name", - HeaderName = null, + HeaderName = "header-name", }; var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options)); // Act - var tokenSet = await tokenStore.GetRequestTokensAsync(httpContext); + var tokens = await tokenStore.GetRequestTokensAsync(httpContext); // Assert - Assert.Equal("cookie-value", tokenSet.CookieToken); - Assert.Null(tokenSet.RequestToken); + Assert.Equal("cookie-value", tokens.CookieToken); + Assert.Equal("header-value", tokens.RequestToken); } [Fact] - public async Task GetRequestTokens_FormContentType_FallbackHeaderToken() + public async Task GetRequestTokens_NoHeaderToken_FallsBackToFormToken() { // Arrange var httpContext = GetHttpContext("cookie-name", "cookie-value"); httpContext.Request.ContentType = "application/x-www-form-urlencoded"; - httpContext.Request.Form = FormCollection.Empty; - httpContext.Request.Headers.Add("header-name", "header-value"); + httpContext.Request.Form = new FormCollection(new Dictionary + { + { "form-field-name", "form-value" }, + }); var options = new AntiforgeryOptions() { @@ -148,7 +153,7 @@ public async Task GetRequestTokens_FormContentType_FallbackHeaderToken() // Assert Assert.Equal("cookie-value", tokens.CookieToken); - Assert.Equal("header-value", tokens.RequestToken); + Assert.Equal("form-value", tokens.RequestToken); } [Fact] @@ -180,7 +185,7 @@ public async Task GetRequestTokens_NonFormContentType_UsesHeaderToken() } [Fact] - public async Task GetRequestTokens_NonFormContentType_NoHeaderToken_ReturnsNullToken() + public async Task GetRequestTokens_NoHeaderToken_NonFormContentType_ReturnsNullToken() { // Arrange var httpContext = GetHttpContext("cookie-name", "cookie-value"); @@ -207,7 +212,7 @@ public async Task GetRequestTokens_NonFormContentType_NoHeaderToken_ReturnsNullT } [Fact] - public async Task GetRequestTokens_BothFieldsEmpty_ReturnsNullTokens() + public async Task GetRequestTokens_BothHeaderValueAndFormFieldsEmpty_ReturnsNullTokens() { // Arrange var httpContext = GetHttpContext("cookie-name", "cookie-value"); @@ -231,35 +236,6 @@ public async Task GetRequestTokens_BothFieldsEmpty_ReturnsNullTokens() Assert.Null(tokenSet.RequestToken); } - [Fact] - public async Task GetFormToken_FormFieldIsValid_ReturnsToken() - { - // Arrange - var httpContext = GetHttpContext("cookie-name", "cookie-value"); - httpContext.Request.ContentType = "application/x-www-form-urlencoded"; - httpContext.Request.Form = new FormCollection(new Dictionary - { - { "form-field-name", "form-value" }, - }); - httpContext.Request.Headers.Add("header-name", "header-value"); // form value has priority. - - var options = new AntiforgeryOptions() - { - CookieName = "cookie-name", - FormFieldName = "form-field-name", - HeaderName = "header-name", - }; - - var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options)); - - // Act - var tokens = await tokenStore.GetRequestTokensAsync(httpContext); - - // Assert - Assert.Equal("cookie-value", tokens.CookieToken); - Assert.Equal("form-value", tokens.RequestToken); - } - [Theory] [InlineData(true, true)] [InlineData(false, null)]