diff --git a/src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs index 3ba59e115..ed0de8596 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,11 @@ public class AspNetRequestUrlRenderer : AspNetLayoutRendererBase /// public bool IncludeScheme { get; set; } = true; + /// + /// To specify whether to use raw path and full query (for ASP.NET Core only). Default is false. + /// + public bool UseRawTarget { get; set; } = false; + /// /// Renders the Request URL from the HttpRequest /// @@ -112,11 +118,20 @@ 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(httpRequest.QueryString.Value); + builder.Append(httpRequestFeature.RawTarget); + } + else + { + builder.Append(httpRequest.PathBase.ToUriComponent()); + builder.Append(httpRequest.Path.ToUriComponent()); + + if (IncludeQueryString) + { + builder.Append(httpRequest.QueryString.Value); + } } } #endif diff --git a/tests/Shared/LayoutRenderers/AspNetRequestUrlRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestUrlRendererTests.cs index 6752d8290..5cbedf652 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 @@ -147,7 +151,30 @@ public void UrlPresentRenderNonEmpty_ExcludeHost_IncludeQueryString() Assert.Equal("http:///Test.asp?t=1", result); } - private static AspNetRequestUrlRenderer CreateRenderer(string hostBase, string queryString = "", string scheme = "http", string page = "/", string pathBase = "") + [Fact] + public void UrlPresentRenderNonEmpty_UseRawTarget() + { + var renderer = CreateRenderer("www.google.com:80", "?t=1", "http", "/Test.asp", rawTarget: "/rawTarget"); + renderer.UseRawTarget = true; + + 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); + } + + private static AspNetRequestUrlRenderer CreateRenderer(string hostBase, string queryString = "", string scheme = "http", string page = "/", string pathBase = "", string rawTarget = null) { var (renderer, httpContext) = CreateWithHttpContext(); @@ -160,8 +187,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 } }