diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 854de575..a029ef21 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -4,10 +4,20 @@ See also [releases](https://github.com/NLog/NLog.Web/releases) and [milestones]( Date format: (year/month/day) +### v4.9.2-aspnetcore (2020/04/20) +- [#546](https://github.com/NLog/NLog.Web/pull/546) ${aspnet-request-ip}: make ForwardedForHeader configurable (@304NotModified) +- [#546](https://github.com/NLog/NLog.Web/pull/546) ${aspnet-request-url} Added UseRawTarget option (@sm-g, @304NotModified) + +### v4.9.2-aspnet4 (2020/04/20) +- [#546](https://github.com/NLog/NLog.Web/pull/546) ${aspnet-request-ip}: make ForwardedForHeader configurable (@304NotModified) + ### v4.9.1-aspnetcore (2020/03/25) -- [#524](https://github.com/NLog/NLog.Web/pull/524) ${aspnet-traceidentifier} should automatically check Activity.Current.Id for NetCore3 (@snakefoot) +- [#524](https://github.com/NLog/NLog.Web/pull/524) ${aspnet-traceidentifier} should automatically check Activity.Current.Id for .NET Core 3 (@snakefoot) - [#527](https://github.com/NLog/NLog.Web/pull/527) Improved AddNLog with LoggingConfiguration to handle custom LogFactory (@snakefoot) +### v4.9.1-aspnet4 (2020/03/25) +- Updated dependencies + ### v4.9.0-aspnetcore (2019/10/11) This version adds support for ASP.NET Core 3 and some nice renderers has been added and improved! diff --git a/appveyor.yml b/appveyor.yml index d9dfcefd..55d293c3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 4.9.1.{build} +version: 4.9.2.{build} clone_folder: c:\projects\nlogweb configuration: Release image: Visual Studio 2019 @@ -22,7 +22,7 @@ skip_tags: true build_script: - ps: | - $versionPrefix = "4.9.1" + $versionPrefix = "4.9.2" $versionSuffix = "" $versionBuild = $versionPrefix + "." + ${env:APPVEYOR_BUILD_NUMBER} $versionNuget = $versionPrefix diff --git a/src/NLog.Web.AspNetCore/NLog.Web.AspNetCore.csproj b/src/NLog.Web.AspNetCore/NLog.Web.AspNetCore.csproj index 40a1b424..76836095 100644 --- a/src/NLog.Web.AspNetCore/NLog.Web.AspNetCore.csproj +++ b/src/NLog.Web.AspNetCore/NLog.Web.AspNetCore.csproj @@ -20,8 +20,10 @@ Supported platforms: NLog.Web.AspNetCore logging;log;NLog;web;aspnet;aspnetcore;MVC;Microsoft.Extensions.Logging;httpcontext;session -- ${aspnet-traceidentifier} should automatically check Activity.Current.Id for NetCore3 (@snakefoot) -- Improved AddNLog with LoggingConfiguration to handle custom LogFactory (@snakefoot) +- ${aspnet-request-ip}: make ForwardedForHeader configurable (@304NotModified) +- ${aspnet-request-url} Added UseRawTarget option (@sm-g, @304NotModified) + +See also https://github.com/NLog/NLog.Web/releases https://nlog-project.org/N.png https://github.com/NLog/NLog.Web diff --git a/src/NLog.Web/NLog.Web.csproj b/src/NLog.Web/NLog.Web.csproj index fb0bbbed..9a7c6798 100644 --- a/src/NLog.Web/NLog.Web.csproj +++ b/src/NLog.Web/NLog.Web.csproj @@ -19,6 +19,8 @@ For ASP.NET Core: Check https://www.nuget.org/packages/NLog.Web.AspNetCore nlog;log;logging;target;layoutrenderer;web;asp.net;MVC;httpcontext + See https://github.com/NLog/NLog.Web/releases + https://nlog-project.org/N.png https://github.com/NLog/NLog.Web @@ -36,7 +38,7 @@ For ASP.NET Core: Check https://www.nuget.org/packages/NLog.Web.AspNetCore true - + diff --git a/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs index fc09be8d..f582a3f7 100644 --- a/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs +++ b/src/Shared/LayoutRenderers/AspNetRequestIpLayoutRenderer.cs @@ -1,11 +1,15 @@ using System; +using System.ComponentModel; using System.Text; + using NLog.Config; using NLog.LayoutRenderers; +using NLog.Layouts; using NLog.Web.Internal; #if ASP_NET_CORE using Microsoft.AspNetCore.Http; - +#else +using System.Web; #endif namespace NLog.Web.LayoutRenderers @@ -22,10 +26,14 @@ namespace NLog.Web.LayoutRenderers [ThreadSafe] public class AspNetRequestIpLayoutRenderer : AspNetLayoutRendererBase { - private const string ForwardedForHeader = "X-Forwarded-For"; + /// + /// The header name to check for the Forwarded-For. Default "X-Forwarded-For". Needs + /// + [DefaultValue("X-Forwarded-For")] + public Layout ForwardedForHeader { get; set; } = "X-Forwarded-For"; /// - /// Gets or sets whether the renderer should check value of X-Forwarded-For header + /// Gets or sets whether the renderer should check value of header /// /// public bool CheckForwardedForHeader { get; set; } @@ -43,7 +51,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) return; } - var ip = CheckForwardedForHeader ? TryLookupForwardHeader(request) : string.Empty; + var ip = CheckForwardedForHeader && ForwardedForHeader != null ? TryLookupForwardHeader(request, logEvent) : string.Empty; if (string.IsNullOrEmpty(ip)) { @@ -58,9 +66,10 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) } #if !ASP_NET_CORE - string TryLookupForwardHeader(System.Web.HttpRequestBase httpRequest) + string TryLookupForwardHeader(HttpRequestBase httpRequest, LogEventInfo logEvent) { - var forwardedHeader = httpRequest.Headers[ForwardedForHeader]; + var headerName = ForwardedForHeader.Render(logEvent); + var forwardedHeader = httpRequest.Headers[headerName]; if (!string.IsNullOrEmpty(forwardedHeader)) { @@ -74,11 +83,12 @@ string TryLookupForwardHeader(System.Web.HttpRequestBase httpRequest) return string.Empty; } #else - private string TryLookupForwardHeader(HttpRequest httpRequest) + private string TryLookupForwardHeader(HttpRequest httpRequest, LogEventInfo logEvent) { - if (httpRequest.Headers?.ContainsKey(ForwardedForHeader) == true) + var headerName = ForwardedForHeader.Render(logEvent); + if (httpRequest.Headers?.ContainsKey(headerName) == true) { - var forwardedHeaders = httpRequest.Headers.GetCommaSeparatedValues(ForwardedForHeader); + var forwardedHeaders = httpRequest.Headers.GetCommaSeparatedValues(headerName); if (forwardedHeaders.Length > 0) { return forwardedHeaders[0]; diff --git a/src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs index 3ba59e11..6c60aeca 100644 --- a/src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs +++ b/src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs @@ -5,6 +5,7 @@ using NLog.Web.Internal; #if ASP_NET_CORE using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; #else using System.Collections.Specialized; @@ -24,7 +25,7 @@ namespace NLog.Web.LayoutRenderers /// ${aspnet-request-url:IncludePort=true} - produces http://www.exmaple.com:80/ /// ${aspnet-request-url:IncludePort=false} - produces http://www.exmaple.com/ /// ${aspnet-request-url:IncludeScheme=false} - produces www.exmaple.com/ - /// ${aspnet-request-url:IncludePort=true:IncludeQueryString=true} - produces http://www.exmaple.com:80/?t=1 + /// ${aspnet-request-url:IncludePort=true:IncludeQueryString=true} - produces http://www.exmaple.com:80/?t=1 /// /// [LayoutRenderer("aspnet-request-url")] @@ -37,7 +38,7 @@ public class AspNetRequestUrlRenderer : AspNetLayoutRendererBase public bool IncludeQueryString { get; set; } = false; /// - /// To specify whether to include /exclude the Port. Default is false. + /// To specify whether to include / exclude the Port. Default is false. /// public bool IncludePort { get; set; } = false; @@ -51,6 +52,16 @@ public class AspNetRequestUrlRenderer : AspNetLayoutRendererBase /// public bool IncludeScheme { get; set; } = true; +#if ASP_NET_CORE + + /// + /// To specify whether to use raw path and full query. Default is false. + /// See https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features.ihttprequestfeature.rawtarget + /// + public bool UseRawTarget { get; set; } = false; + +#endif + /// /// Renders the Request URL from the HttpRequest /// @@ -68,6 +79,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) } #if !ASP_NET_CORE + private void RenderUrl(HttpRequestBase httpRequest, StringBuilder builder) { var url = httpRequest.Url; @@ -112,13 +124,22 @@ private void RenderUrl(HttpRequest httpRequest, StringBuilder builder) builder.Append(httpRequest.Host.Port.Value); } - builder.Append(httpRequest.PathBase.ToUriComponent()); - builder.Append(httpRequest.Path.ToUriComponent()); - if (IncludeQueryString) + IHttpRequestFeature httpRequestFeature; + if (UseRawTarget && (httpRequestFeature = httpRequest.HttpContext.Features.Get()) != null) + { + builder.Append(httpRequestFeature.RawTarget); + } + else { - builder.Append(httpRequest.QueryString.Value); + builder.Append(httpRequest.PathBase.ToUriComponent()); + builder.Append(httpRequest.Path.ToUriComponent()); + + if (IncludeQueryString) + { + builder.Append(httpRequest.QueryString.Value); + } } } #endif } -} \ No newline at end of file +} diff --git a/tests/NLog.Web.Tests/NLog.Web.Tests.csproj b/tests/NLog.Web.Tests/NLog.Web.Tests.csproj index 66fb5265..43a02a5a 100644 --- a/tests/NLog.Web.Tests/NLog.Web.Tests.csproj +++ b/tests/NLog.Web.Tests/NLog.Web.Tests.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs index c34ac0bb..ea342000 100644 --- a/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs +++ b/tests/Shared/LayoutRenderers/AspNetRequestIpLayoutRendererTests.cs @@ -88,6 +88,31 @@ public void ForwardedForHeaderContainsMultipleEntriesRenderFirstValue() // Act string result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("127.0.0.1", result); + } + + [Fact] + public void ForwardedForHeaderPresentWithCustomRenderForwardedValue() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + +#if !ASP_NET_CORE + httpContext.Request.ServerVariables.Returns(new NameValueCollection {{"REMOTE_ADDR", "192.0.0.0"}}); + httpContext.Request.Headers.Returns( + new NameValueCollection {{"header2", "127.0.0.1"}}); +#else + var headers = new HeaderDict(); + headers.Add("header2", new StringValues("127.0.0.1")); + httpContext.Request.Headers.Returns(callinfo => headers); +#endif + renderer.CheckForwardedForHeader = true; + renderer.ForwardedForHeader = "header2"; + + // Act + string result = renderer.Render(new LogEventInfo()); + // Assert Assert.Equal("127.0.0.1", result); } diff --git a/tests/Shared/LayoutRenderers/AspNetRequestUrlRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestUrlRendererTests.cs index 6752d829..524f9daf 100644 --- a/tests/Shared/LayoutRenderers/AspNetRequestUrlRendererTests.cs +++ b/tests/Shared/LayoutRenderers/AspNetRequestUrlRendererTests.cs @@ -10,11 +10,15 @@ using Microsoft.Extensions.Primitives; using HttpContextBase = Microsoft.AspNetCore.Http.HttpContext; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; #endif using NLog.Web.LayoutRenderers; using NSubstitute; + using Xunit; +using System.IO; + namespace NLog.Web.Tests.LayoutRenderers { public class AspNetRequestUrlRendererTests : LayoutRenderersTestBase @@ -146,8 +150,32 @@ public void UrlPresentRenderNonEmpty_ExcludeHost_IncludeQueryString() Assert.Equal("http:///Test.asp?t=1", result); } +#if ASP_NET_CORE + [Fact] + public void UrlPresentRenderNonEmpty_UseRawTarget() + { + var renderer = CreateRenderer("www.google.com:80", "?t=1", "http", "/Test.asp", rawTarget: "/rawTarget"); + renderer.UseRawTarget = true; - private static AspNetRequestUrlRenderer CreateRenderer(string hostBase, string queryString = "", string scheme = "http", string page = "/", string pathBase = "") + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + Assert.Equal("http://www.google.com/rawTarget", result); + } + + [Fact] + public void UrlPresentRenderNonEmpty_UseRawTarget_IncludeQueryString() + { + var renderer = CreateRenderer("www.google.com:80", "?t=1", "http", "/Test.asp", rawTarget: "/rawTarget"); + renderer.UseRawTarget = true; + renderer.IncludeQueryString = true; + + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + Assert.Equal("http://www.google.com/rawTarget", result); + } +#endif + + private static AspNetRequestUrlRenderer CreateRenderer(string hostBase, string queryString = "", string scheme = "http", string page = "/", string pathBase = "", string rawTarget = null) { var (renderer, httpContext) = CreateWithHttpContext(); @@ -160,8 +188,40 @@ private static AspNetRequestUrlRenderer CreateRenderer(string hostBase, string q httpContext.Request.QueryString.Returns(new QueryString(queryString)); httpContext.Request.Host.Returns(new HostString(hostBase)); httpContext.Request.Scheme.Returns(scheme); + + if (rawTarget != null) + { + httpContext.Request.HttpContext.Returns(httpContext); + + var httpRequestFeature = new HttpRequestFeatureMock(); + httpRequestFeature.RawTarget = rawTarget; + var collection = new FeatureCollection(); + collection.Set(httpRequestFeature); + httpContext.Features.Returns(collection); + } #endif return renderer; } + +#if ASP_NET_CORE + + private class HttpRequestFeatureMock : IHttpRequestFeature + { + #region Implementation of IHttpRequestFeature + + public Stream Body { get; set; } + public IHeaderDictionary Headers { get; set; } + public string Method { get; set; } + public string Path { get; set; } + public string PathBase { get; set; } + public string Protocol { get; set; } + public string QueryString { get; set; } + public string RawTarget { get; set; } + public string Scheme { get; set; } + + #endregion Implementation of IHttpRequestFeature + } + +#endif } }