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 4 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
6 changes: 6 additions & 0 deletions src/Shared/Internal/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;

#if! ASP_NET_CORE2
using Microsoft.AspNetCore.Http.Headers;
#endif

#endif
using NLog.Common;

Expand Down Expand Up @@ -98,6 +103,7 @@ internal static string GetString(this ISession session, string key)
}
#endif


#if !ASP_NET_CORE
internal static HttpSessionStateBase TryGetSession(this HttpContextBase context)
{
Expand Down
194 changes: 194 additions & 0 deletions src/Shared/LayoutRenderers/AspNetResponseCookieLayoutRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
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 (cookies?.Count > 0)
{
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 wrap getting cookies from the HTTP Response for both Framework and Core
/// </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);
}
}
}

#elif ASP_NET_CORE2

/// <summary>
/// Method to wrap getting cookies from the HTTP Response for both Framework and Core
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
protected IList<SetCookieHeaderValue> GetCookies(HttpResponse response)
{
var queryResults = response.Headers.Where(row => row.Key == "Set-Cookie").ToList();
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
var cookieList = new List<SetCookieHeaderValue>();
foreach (var row in queryResults)
{
cookieList.Add(new SetCookieHeaderValue(new StringSegment(row.Key), new StringSegment(row.Value)));
}
return cookieList;
}


#elif ASP_NET_CORE3
/// <summary>
/// Method to wrap getting cookies from the HTTP Response for both Framework and Core
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
protected IList<SetCookieHeaderValue> GetCookies(HttpResponse response)
{
return response.GetTypedHeaders().SetCookie;
}
#endif

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

private IEnumerable<KeyValuePair<string, string>> GetCookieValues(IList<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