Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASP.NET Response Cookie Layout Renderer #789

Merged
merged 23 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/NLog.Web.AspNetCore/NLog.Web.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ NLog 5 release post: https://nlog-project.org/2021/08/25/nlog-5-0-preview1-ready
<DefineConstants>$(DefineConstants);ASP_NET_CORE;ASP_NET_CORE3</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net461' ">
<!-- Fixed to 2.1.0 as 2.1 is Long Term Supported (LTS) and works with vanilla .NET Core 2.1 SDK -->
Expand Down
180 changes: 180 additions & 0 deletions src/Shared/LayoutRenderers/AspNetResponseCookieLayoutRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Web.Enums;
using NLog.Web.Internal;
#if !ASP_NET_CORE
using System.Collections.Specialized;
using System.Web;
using Cookies = System.Web.HttpCookieCollection;
#else
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
#endif

namespace NLog.Web.LayoutRenderers
{
/// <summary>
/// ASP.NET Response Cookie
/// </summary>
/// <example>
/// <para>Example usage of ${aspnet-response-cookie}</para>
/// <code lang="NLog Layout Renderer">
/// ${aspnet-response-cookie:OutputFormat=Flat}
/// ${aspnet-response-cookie:OutputFormat=JsonArray}
/// ${aspnet-response-cookie:OutputFormat=JsonDictionary}
/// ${aspnet-response-cookie:OutputFormat=JsonDictionary:CookieNames=username}
/// ${aspnet-response-cookie:OutputFormat=JsonDictionary:Exclude=access_token}
/// </code>
/// </example>
[LayoutRenderer("aspnet-response-cookie")]
public class AspNetResponseCookieLayoutRenderer : AspNetLayoutMultiValueRendererBase
{
/// <summary>
/// Cookie names to be rendered.
/// If <c>null</c> or empty array, all cookies will be rendered.
/// </summary>
public List<string> CookieNames { get; set; }

/// <summary>
/// Gets or sets the keys to exclude from the output. If omitted, none are excluded.
/// </summary>
/// <docgen category='Rendering Options' order='10' />
#if ASP_NET_CORE
public ISet<string> Exclude { get; set; }
#else
public HashSet<string> Exclude { get; set; }
#endif

/// <summary>
/// Initializes a new instance of the <see cref="AspNetResponseCookieLayoutRenderer" /> class.
/// </summary>
public AspNetResponseCookieLayoutRenderer()
{
Exclude = new HashSet<string>(new[] { "AUTH", "SESS_ID" }, StringComparer.OrdinalIgnoreCase);
}

/// <summary>
/// Renders the ASP.NET Cookie appends it to the specified <see cref="StringBuilder" />.
/// </summary>
/// <param name="builder">The <see cref="StringBuilder" /> to append the rendered data to.</param>
/// <param name="logEvent">Logging event.</param>
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpResponse = HttpContextAccessor.HttpContext.TryGetResponse();
if (httpResponse == null)
{
return;
}

var cookies = GetCookies(httpResponse);
#if ASP_NET_CORE
if (cookies.Any())
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
#else
if(cookies.Count > 0)
#endif
{
bool checkForExclude = (CookieNames == null || CookieNames.Count == 0) && Exclude?.Count > 0;
var cookieValues = GetCookieValues(cookies, checkForExclude);
SerializePairs(cookieValues, builder, logEvent);
}
}

#if !ASP_NET_CORE

/// <summary>
/// Method to get cookies for .NET Framework
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
protected Cookies GetCookies(HttpResponseBase response)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
return response.Cookies;
}

private List<string> GetCookieNames(HttpCookieCollection cookies)
{
return CookieNames?.Count > 0 ? CookieNames : cookies.Keys.Cast<string>().ToList();
}

private IEnumerable<KeyValuePair<string, string>> GetCookieValues(HttpCookieCollection cookies, bool checkForExclude)
{
var cookieNames = GetCookieNames(cookies);
foreach (var cookieName in cookieNames)
{
if (checkForExclude && Exclude.Contains(cookieName))
continue;

var httpCookie = cookies[cookieName];
if (httpCookie == null)
{
continue;
}

if (OutputFormat != AspNetRequestLayoutOutputFormat.Flat)
{
// Split multi-valued cookie, as allowed for in the HttpCookie API for backwards compatibility with classic ASP
var isFirst = true;
foreach (var multiValueKey in httpCookie.Values.AllKeys)
{
var cookieKey = multiValueKey;
if (isFirst)
{
cookieKey = cookieName;
isFirst = false;
}
yield return new KeyValuePair<string, string>(cookieKey, httpCookie.Values[multiValueKey]);
}
}
else
{
yield return new KeyValuePair<string, string>(cookieName, httpCookie.Value);
}
}
}

#else
/// <summary>
/// Method to get cookies for all ASP.NET Core versions
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
protected IList<SetCookieHeaderValue> GetCookies(HttpResponse response)
{
var queryResults = response.Headers[HeaderNames.SetCookie];
if (queryResults.Count > 0 && SetCookieHeaderValue.TryParseList(queryResults, out var result))
return result;
else
return Array.Empty<SetCookieHeaderValue>();
}

private List<string> GetCookieNames(IEnumerable<SetCookieHeaderValue> cookies)
{
return CookieNames?.Count > 0 ? CookieNames : cookies.Select(row => row.Name.ToString()).ToList();
}

private IEnumerable<KeyValuePair<string, string>> GetCookieValues(IEnumerable<SetCookieHeaderValue> cookies, bool checkForExclude)
{
var cookieNames = GetCookieNames(cookies);
foreach (var cookieName in cookieNames)
{
if (checkForExclude && Exclude.Contains(cookieName))
continue;

var httpCookie = cookies.SingleOrDefault(cookie => cookie.Name.ToString() == cookieName);
if (httpCookie == null)
{
continue;
}

yield return new KeyValuePair<string, string>(cookieName, httpCookie.Value.ToString());
}
}
#endif
}
}
Loading