| File: CookieTempDataProvider.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.ViewFeatures\src\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj (Microsoft.AspNetCore.Mvc.ViewFeatures) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc.ViewFeatures.Infrastructure; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; /// <summary> /// Provides data from cookie to the current <see cref="ITempDataDictionary"/> object. /// </summary> public partial class CookieTempDataProvider : ITempDataProvider { /// <summary> /// The name of the cookie. /// </summary> public static readonly string CookieName = ".AspNetCore.Mvc.CookieTempDataProvider"; private const string Purpose = "Microsoft.AspNetCore.Mvc.CookieTempDataProviderToken.v1"; private readonly IDataProtector _dataProtector; private readonly ILogger _logger; private readonly TempDataSerializer _tempDataSerializer; private readonly ChunkingCookieManager _chunkingCookieManager; private readonly CookieTempDataProviderOptions _options; /// <summary> /// Initializes a new instance of <see cref="CookieTempDataProvider"/>. /// </summary> /// <param name="dataProtectionProvider">The <see cref="IDataProtectionProvider"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="options">The <see cref="CookieTempDataProviderOptions"/>.</param> /// <param name="tempDataSerializer">The <see cref="TempDataSerializer"/>.</param> public CookieTempDataProvider( IDataProtectionProvider dataProtectionProvider, ILoggerFactory loggerFactory, IOptions<CookieTempDataProviderOptions> options, TempDataSerializer tempDataSerializer) { _dataProtector = dataProtectionProvider.CreateProtector(Purpose); _logger = loggerFactory.CreateLogger<CookieTempDataProvider>(); _tempDataSerializer = tempDataSerializer; _chunkingCookieManager = new ChunkingCookieManager(); _options = options.Value; } /// <summary> /// Loads the temp data from the request. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <returns>The temp data.</returns> public IDictionary<string, object> LoadTempData(HttpContext context) { ArgumentNullException.ThrowIfNull(context); if (context.Request.Cookies.ContainsKey(_options.Cookie.Name)) { // The cookie we use for temp data is user input, and might be invalid in many ways. // // Since TempData is a best-effort system, we don't want to throw and get a 500 if the cookie is // bad, we will just clear it and ignore the exception. The common case that we've identified for // this is misconfigured data protection settings, which can cause the key used to create the // cookie to no longer be available. try { var encodedValue = _chunkingCookieManager.GetRequestCookie(context, _options.Cookie.Name); if (!string.IsNullOrEmpty(encodedValue)) { var protectedData = WebEncoders.Base64UrlDecode(encodedValue); var unprotectedData = _dataProtector.Unprotect(protectedData); var tempData = _tempDataSerializer.Deserialize(unprotectedData); Log.TempDataCookieLoadSuccess(_logger, _options.Cookie.Name); return tempData; } } catch (Exception ex) { Log.TempDataCookieLoadFailure(_logger, _options.Cookie.Name, ex); // If we've failed, we want to try and clear the cookie so that this won't keep happening // over and over. if (!context.Response.HasStarted) { _chunkingCookieManager.DeleteCookie(context, _options.Cookie.Name, _options.Cookie.Build(context)); } } } Log.TempDataCookieNotFound(_logger, _options.Cookie.Name); return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } /// <summary> /// Save the temp data to the request. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> /// <param name="values">The values.</param> public void SaveTempData(HttpContext context, IDictionary<string, object> values) { ArgumentNullException.ThrowIfNull(context); var cookieOptions = _options.Cookie.Build(context); SetCookiePath(context, cookieOptions); var hasValues = (values != null && values.Count > 0); if (hasValues) { var bytes = _tempDataSerializer.Serialize(values); bytes = _dataProtector.Protect(bytes); var encodedValue = WebEncoders.Base64UrlEncode(bytes); _chunkingCookieManager.AppendResponseCookie(context, _options.Cookie.Name, encodedValue, cookieOptions); } else { _chunkingCookieManager.DeleteCookie(context, _options.Cookie.Name, cookieOptions); } } private void SetCookiePath(HttpContext httpContext, CookieOptions cookieOptions) { if (!string.IsNullOrEmpty(_options.Cookie.Path)) { cookieOptions.Path = _options.Cookie.Path; } else { var pathBase = httpContext.Request.PathBase.ToString(); if (!string.IsNullOrEmpty(pathBase)) { cookieOptions.Path = pathBase; } } } private static partial class Log { [LoggerMessage(1, LogLevel.Debug, "The temp data cookie {CookieName} was not found.", EventName = "TempDataCookieNotFound")] public static partial void TempDataCookieNotFound(ILogger logger, string cookieName); [LoggerMessage(2, LogLevel.Debug, "The temp data cookie {CookieName} was used to successfully load temp data.", EventName = "TempDataCookieLoadSuccess")] public static partial void TempDataCookieLoadSuccess(ILogger logger, string cookieName); [LoggerMessage(3, LogLevel.Warning, "The temp data cookie {CookieName} could not be loaded.", EventName = "TempDataCookieLoadFailure")] public static partial void TempDataCookieLoadFailure(ILogger logger, string cookieName, Exception exception); } }