diff --git a/src/Geta.404Handler/Core/RequestHandler.cs b/src/Geta.404Handler/Core/RequestHandler.cs index bad740b..516818d 100644 --- a/src/Geta.404Handler/Core/RequestHandler.cs +++ b/src/Geta.404Handler/Core/RequestHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Geta Digital. All rights reserved. +// Copyright (c) Geta Digital. All rights reserved. // Licensed under Apache-2.0. See the LICENSE file in the project root for more information using System; @@ -21,6 +21,7 @@ public class RequestHandler private readonly IConfiguration _configuration; private const string HandledRequestItemKey = "404handler:handled"; + private const string RedirectedOnceKey = "404handler:redirected"; private static readonly ILogger Logger = LogManager.GetLogger(); public RequestHandler( @@ -86,10 +87,18 @@ public virtual void Handle(HttpContextBase context) return; } + if (context.Items.Contains(RedirectedOnceKey)) + { + LogDebug("Redirect already attempted in this request, skipping to avoid loop.", context); + return; + } + var canHandleRedirect = HandleRequest(context.Request.UrlReferrer, notFoundUri, out var newUrl); if (canHandleRedirect && newUrl.State == (int)RedirectState.Saved) { LogDebug("Handled saved URL", context); + + context.Items[RedirectedOnceKey] = true; context .ClearServerError() @@ -140,14 +149,26 @@ public virtual bool HandleRequest(Uri referrer, Uri urlNotFound, out CustomRedir if (redirect.State.Equals((int)RedirectState.Saved)) { - // Found it, however, we need to make sure we're not running in an - // infinite loop. The new url must not be the referrer to this page - if (string.Compare(redirect.NewUrl, urlNotFound.PathAndQuery, StringComparison.InvariantCultureIgnoreCase) != 0) + // Prevent redirect loop + var currentPath = urlNotFound.PathAndQuery; + var newPath = redirect.NewUrl; + + if (string.Equals(newPath, currentPath, StringComparison.OrdinalIgnoreCase)) { + Logger.Debug($"Redirect leads back to the same path: {newPath}. Skipping to avoid loop."); + return false; + } - foundRedirect = redirect; - return true; + // Avoid redirecting back to the referrer (loop possibility) + if (referrer != null && + string.Equals(referrer.PathAndQuery, newPath, StringComparison.OrdinalIgnoreCase)) + { + Logger.Debug($"Redirect target matches referrer: {newPath}. Skipping to avoid loop."); + return false; } + + foundRedirect = redirect; + return true; } } else diff --git a/tests/Geta.404Handler.Tests/RequestHandlerTests.cs b/tests/Geta.404Handler.Tests/RequestHandlerTests.cs index ee8d27b..39c187e 100644 --- a/tests/Geta.404Handler.Tests/RequestHandlerTests.cs +++ b/tests/Geta.404Handler.Tests/RequestHandlerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using BVNetwork.NotFound.Core; using BVNetwork.NotFound.Core.Configuration; @@ -226,6 +226,28 @@ public void HandleRequest_returns_false_when_redirect_is_same_as_not_found() Assert.False(actual); } + [Fact] + public void HandleRequest_returns_false_when_redirect_matches_referrer() + { + // Arrange + var notFoundUri = new Uri("http://example.com/missing-page"); + var referrerUri = new Uri("http://example.com/redirect-target"); + + var redirect = new CustomRedirect(notFoundUri.ToString(), (int)RedirectState.Saved, 1) + { + NewUrl = referrerUri.PathAndQuery + }; + + WhenRedirectFound(redirect); + + // Act + var actual = _sut.HandleRequest(referrerUri, notFoundUri, out var foundRedirect); + + // Assert + Assert.False(actual); + Assert.Null(foundRedirect); + } + private void WhenRedirectFound(CustomRedirect redirect) { A.CallTo(() => _redirectHandler.Find(A._)).Returns(redirect); @@ -338,4 +360,4 @@ private void AssertNotHandled(HttpContextBase context) Assert.False(context.Response.TrySkipIisCustomErrors); } } -} \ No newline at end of file +}