128 lines
5.3 KiB
C#
128 lines
5.3 KiB
C#
// 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.Collections.Generic;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
using Microsoft.AspNetCore.Internal;
|
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|
{
|
|
/// <summary>
|
|
/// Provides data from cookie to the current <see cref="ITempDataDictionary"/> object.
|
|
/// </summary>
|
|
public class CookieTempDataProvider : ITempDataProvider
|
|
{
|
|
public static readonly string CookieName = ".AspNetCore.Mvc.CookieTempDataProvider";
|
|
private static readonly 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;
|
|
|
|
public CookieTempDataProvider(
|
|
IDataProtectionProvider dataProtectionProvider,
|
|
ILoggerFactory loggerFactory,
|
|
IOptions<CookieTempDataProviderOptions> options)
|
|
{
|
|
_dataProtector = dataProtectionProvider.CreateProtector(Purpose);
|
|
_logger = loggerFactory.CreateLogger<CookieTempDataProvider>();
|
|
_tempDataSerializer = new TempDataSerializer();
|
|
_chunkingCookieManager = new ChunkingCookieManager();
|
|
_options = options.Value;
|
|
}
|
|
|
|
public IDictionary<string, object> LoadTempData(HttpContext context)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(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 = Base64UrlTextEncoder.Decode(encodedValue);
|
|
var unprotectedData = _dataProtector.Unprotect(protectedData);
|
|
var tempData = _tempDataSerializer.Deserialize(unprotectedData);
|
|
|
|
_logger.TempDataCookieLoadSuccess(_options.Cookie.Name);
|
|
return tempData;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.TempDataCookieLoadFailure(_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));
|
|
}
|
|
}
|
|
}
|
|
|
|
_logger.TempDataCookieNotFound(_options.Cookie.Name);
|
|
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public void SaveTempData(HttpContext context, IDictionary<string, object> values)
|
|
{
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(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 = Base64UrlTextEncoder.Encode(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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|