Moves RoutableDocumentFilter along with tests
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class RoutableDocumentFilterTests
|
||||
{
|
||||
private GlobalSettings GetGlobalSettings() => new GlobalSettings();
|
||||
|
||||
private IHostingEnvironment GetHostingEnvironment()
|
||||
{
|
||||
var hostingEnv = new Mock<IHostingEnvironment>();
|
||||
hostingEnv.Setup(x => x.ToAbsolute(It.IsAny<string>())).Returns((string virtualPath) => virtualPath.TrimStart('~', '/'));
|
||||
return hostingEnv.Object;
|
||||
}
|
||||
|
||||
[TestCase("/umbraco/editContent.aspx")]
|
||||
[TestCase("/install/default.aspx")]
|
||||
[TestCase("/install/")]
|
||||
[TestCase("/install")]
|
||||
[TestCase("/install/?installStep=asdf")]
|
||||
[TestCase("/install/test.aspx")]
|
||||
public void Is_Reserved_Path_Or_Url(string url)
|
||||
{
|
||||
var routableDocFilter = new RoutableDocumentFilter(
|
||||
GetGlobalSettings(),
|
||||
GetHostingEnvironment(),
|
||||
new DefaultEndpointDataSource());
|
||||
|
||||
// Will be false if it is a reserved path
|
||||
Assert.IsFalse(routableDocFilter.IsDocumentRequest(url));
|
||||
}
|
||||
|
||||
[TestCase("/base/somebasehandler")]
|
||||
[TestCase("/")]
|
||||
[TestCase("/home.aspx")]
|
||||
[TestCase("/umbraco-test")]
|
||||
[TestCase("/install-test")]
|
||||
[TestCase("/install.aspx")]
|
||||
public void Is_Not_Reserved_Path_Or_Url(string url)
|
||||
{
|
||||
var routableDocFilter = new RoutableDocumentFilter(
|
||||
GetGlobalSettings(),
|
||||
GetHostingEnvironment(),
|
||||
new DefaultEndpointDataSource());
|
||||
|
||||
// Will be true if it's not reserved
|
||||
Assert.IsTrue(routableDocFilter.IsDocumentRequest(url));
|
||||
}
|
||||
|
||||
[TestCase("/Do/Not/match", false)]
|
||||
[TestCase("/Umbraco/RenderMvcs", false)]
|
||||
[TestCase("/Umbraco/RenderMvc", true)]
|
||||
[TestCase("/umbraco/RenderMvc/Index", true)]
|
||||
[TestCase("/Umbraco/RenderMvc/Index/1234", true)]
|
||||
[TestCase("/Umbraco/RenderMvc/Index/1234/", true)]
|
||||
[TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
|
||||
[TestCase("/api", true)]
|
||||
[TestCase("/api/WebApiTest", true)]
|
||||
[TestCase("/Api/WebApiTest/1234", true)]
|
||||
[TestCase("/api/WebApiTest/Index/1234", false)]
|
||||
public void Is_Reserved_By_Route(string url, bool isReserved)
|
||||
{
|
||||
var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty };
|
||||
|
||||
RouteEndpoint endpoint1 = CreateEndpoint(
|
||||
"Umbraco/RenderMvc/{action?}/{id?}",
|
||||
new { controller = "RenderMvc" },
|
||||
"Umbraco_default",
|
||||
0);
|
||||
|
||||
RouteEndpoint endpoint2 = CreateEndpoint(
|
||||
"api/{controller?}/{id?}",
|
||||
new { action = "Index" },
|
||||
"WebAPI",
|
||||
1);
|
||||
|
||||
var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2);
|
||||
|
||||
var routableDocFilter = new RoutableDocumentFilter(
|
||||
globalSettings,
|
||||
GetHostingEnvironment(),
|
||||
endpointDataSource);
|
||||
|
||||
Assert.AreEqual(
|
||||
!isReserved, // not reserved if it's a document request
|
||||
routableDocFilter.IsDocumentRequest(url));
|
||||
}
|
||||
|
||||
// borrowed from https://github.com/dotnet/aspnetcore/blob/19559e73da2b6d335b864ed2855dd8a0c7a207a0/src/Mvc/Mvc.Core/test/Routing/ControllerLinkGeneratorExtensionsTest.cs#L171
|
||||
private RouteEndpoint CreateEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
string name = null,
|
||||
int order = 0) => new RouteEndpoint(
|
||||
(httpContext) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, null),
|
||||
order,
|
||||
new EndpointMetadataCollection(Array.Empty<object>()),
|
||||
name);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Tests.Common.Builders;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class RoutableDocumentFilterTests : BaseWebTest
|
||||
{
|
||||
[TestCase("/umbraco/editContent.aspx")]
|
||||
[TestCase("/install/default.aspx")]
|
||||
[TestCase("/install/")]
|
||||
[TestCase("/install")]
|
||||
[TestCase("/install/?installStep=asdf")]
|
||||
[TestCase("/install/test.aspx")]
|
||||
public void Is_Reserved_Path_Or_Url(string url)
|
||||
{
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper);
|
||||
Assert.IsTrue(routableDocFilter.IsReservedPathOrUrl(url));
|
||||
}
|
||||
|
||||
[TestCase("/base/somebasehandler")]
|
||||
[TestCase("/")]
|
||||
[TestCase("/home.aspx")]
|
||||
[TestCase("/umbraco-test")]
|
||||
[TestCase("/install-test")]
|
||||
[TestCase("/install.aspx")]
|
||||
public void Is_Not_Reserved_Path_Or_Url(string url)
|
||||
{
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper);
|
||||
Assert.IsFalse(routableDocFilter.IsReservedPathOrUrl(url));
|
||||
}
|
||||
|
||||
[TestCase("/Do/Not/match", false)]
|
||||
[TestCase("/Umbraco/RenderMvcs", false)]
|
||||
[TestCase("/Umbraco/RenderMvc", true)]
|
||||
[TestCase("/Umbraco/RenderMvc/Index", true)]
|
||||
[TestCase("/Umbraco/RenderMvc/Index/1234", true)]
|
||||
[TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
|
||||
[TestCase("/api", true)]
|
||||
[TestCase("/api/WebApiTest", true)]
|
||||
[TestCase("/api/WebApiTest/1234", true)]
|
||||
[TestCase("/api/WebApiTest/Index/1234", false)]
|
||||
public void Is_Reserved_By_Route(string url, bool shouldMatch)
|
||||
{
|
||||
//reset the app config, we only want to test routes not the hard coded paths
|
||||
|
||||
var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty };
|
||||
|
||||
var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper);
|
||||
|
||||
var routes = new RouteCollection();
|
||||
|
||||
routes.MapRoute(
|
||||
"Umbraco_default",
|
||||
"Umbraco/RenderMvc/{action}/{id}",
|
||||
new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional });
|
||||
routes.MapRoute(
|
||||
"WebAPI",
|
||||
"api/{controller}/{id}",
|
||||
new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional });
|
||||
|
||||
|
||||
var context = new FakeHttpContextFactory(url);
|
||||
|
||||
|
||||
Assert.AreEqual(
|
||||
shouldMatch,
|
||||
routableDocFilter.IsReservedPathOrUrl(url, context.HttpContext, routes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ namespace Umbraco.Tests.Routing
|
||||
logger,
|
||||
null, // FIXME: PublishedRouter complexities...
|
||||
Mock.Of<IUmbracoContextFactory>(),
|
||||
new RoutableDocumentFilter(globalSettings, IOHelper),
|
||||
globalSettings,
|
||||
HostingEnvironment
|
||||
);
|
||||
|
||||
@@ -153,7 +153,6 @@
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheTests.cs" />
|
||||
<Compile Include="Routing\MediaUrlProviderTests.cs" />
|
||||
<Compile Include="Routing\RoutableDocumentFilterTests.cs" />
|
||||
<Compile Include="Routing\GetContentUrlsTests.cs" />
|
||||
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
|
||||
<Compile Include="TestHelpers\Stubs\TestUserPasswordConfig.cs" />
|
||||
|
||||
194
src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs
Normal file
194
src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Web.Common.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used to check if the current request is for a front-end request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes.
|
||||
/// </remarks>
|
||||
public sealed class RoutableDocumentFilter
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, bool> _routeChecks = new ConcurrentDictionary<string, bool>();
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly EndpointDataSource _endpointDataSource;
|
||||
private readonly object _routeLocker = new object();
|
||||
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
private object _initLocker = new object();
|
||||
private bool _isInit = false;
|
||||
private HashSet<string> _reservedList;
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutableDocumentFilter"/> class.
|
||||
/// </summary>
|
||||
public RoutableDocumentFilter(GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_endpointDataSource = endpointDataSource;
|
||||
_endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null);
|
||||
}
|
||||
|
||||
private void EndpointsChanged(object value)
|
||||
{
|
||||
lock (_routeLocker)
|
||||
{
|
||||
// try clearing each entry
|
||||
foreach (var r in _routeChecks.Keys.ToList())
|
||||
{
|
||||
_routeChecks.TryRemove(r, out _);
|
||||
}
|
||||
|
||||
// re-register after it has changed so we keep listening
|
||||
_endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the request is a document request (i.e. one that the module should handle)
|
||||
/// </summary>
|
||||
public bool IsDocumentRequest(string absPath)
|
||||
{
|
||||
var maybeDoc = true;
|
||||
|
||||
// a document request should be
|
||||
// /foo/bar/nil
|
||||
// /foo/bar/nil/
|
||||
// /foo/bar/nil.aspx
|
||||
// where /foo is not a reserved path
|
||||
|
||||
// TODO: Remove aspx checks
|
||||
|
||||
// if the path contains an extension that is not .aspx
|
||||
// then it cannot be a document request
|
||||
var extension = Path.GetExtension(absPath);
|
||||
if (maybeDoc && extension.IsNullOrWhiteSpace() == false && !extension.InvariantEquals(".aspx"))
|
||||
{
|
||||
maybeDoc = false;
|
||||
}
|
||||
|
||||
// at that point, either we have no extension, or it is .aspx
|
||||
|
||||
// if the path is reserved then it cannot be a document request
|
||||
if (maybeDoc && IsReservedPathOrUrl(absPath))
|
||||
{
|
||||
maybeDoc = false;
|
||||
}
|
||||
|
||||
return maybeDoc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="absPath">The Path of the URL to check.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
private bool IsReservedPathOrUrl(string absPath)
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _initLocker, () =>
|
||||
{
|
||||
// store references to strings to determine changes
|
||||
var reservedPathsCache = _globalSettings.ReservedPaths;
|
||||
var reservedUrlsCache = _globalSettings.ReservedUrls;
|
||||
|
||||
// add URLs and paths to a new list
|
||||
var newReservedList = new HashSet<string>();
|
||||
foreach (var reservedUrlTrimmed in NormalizePaths(reservedUrlsCache, false))
|
||||
{
|
||||
newReservedList.Add(reservedUrlTrimmed);
|
||||
}
|
||||
|
||||
foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache, true))
|
||||
{
|
||||
newReservedList.Add(reservedPathTrimmed);
|
||||
}
|
||||
|
||||
// use the new list from now on
|
||||
return newReservedList;
|
||||
});
|
||||
|
||||
// The URL should be cleaned up before checking:
|
||||
// * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
|
||||
// * We shouldn't be comparing the query at all
|
||||
if (absPath.Contains('?'))
|
||||
{
|
||||
absPath = absPath.Split('?', StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
}
|
||||
|
||||
if (absPath.Contains('.') == false)
|
||||
{
|
||||
absPath = absPath.EnsureEndsWith('/');
|
||||
}
|
||||
|
||||
// return true if URL starts with an element of the reserved list
|
||||
var isReserved = _reservedList.Any(x => absPath.InvariantStartsWith(x));
|
||||
|
||||
if (isReserved)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if the current request matches a route, if so then it is reserved.
|
||||
var hasRoute = _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath));
|
||||
if (hasRoute)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<string> NormalizePaths(string paths, bool ensureTrailingSlash) => paths
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim().ToLowerInvariant())
|
||||
.Where(x => x.IsNullOrWhiteSpace() == false)
|
||||
.Select(reservedPath =>
|
||||
{
|
||||
var r = _hostingEnvironment.ToAbsolute(reservedPath).Trim().EnsureStartsWith('/');
|
||||
return ensureTrailingSlash
|
||||
? r.EnsureEndsWith('/')
|
||||
: r;
|
||||
})
|
||||
.Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false);
|
||||
|
||||
private bool MatchesEndpoint(string absPath)
|
||||
{
|
||||
// Borrowed from https://stackoverflow.com/a/59550580
|
||||
|
||||
// Return a collection of Microsoft.AspNetCore.Http.Endpoint instances.
|
||||
IEnumerable<RouteEndpoint> routeEndpoints = _endpointDataSource?.Endpoints.Cast<RouteEndpoint>();
|
||||
var routeValues = new RouteValueDictionary();
|
||||
|
||||
// string localPath = new Uri(absPath).LocalPath;
|
||||
|
||||
// To get the matchedEndpoint of the provide url
|
||||
RouteEndpoint matchedEndpoint = routeEndpoints
|
||||
.Where(e => new TemplateMatcher(
|
||||
TemplateParser.Parse(e.RoutePattern.RawText),
|
||||
new RouteValueDictionary())
|
||||
.TryMatch(absPath, routeValues))
|
||||
.OrderBy(c => c.Order)
|
||||
.FirstOrDefault();
|
||||
|
||||
return matchedEndpoint != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.DependencyInjection;
|
||||
using Umbraco.Infrastructure.PublishedCache.DependencyInjection;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Website.Collections;
|
||||
using Umbraco.Web.Website.Controllers;
|
||||
using Umbraco.Web.Website.Routing;
|
||||
@@ -40,6 +41,7 @@ namespace Umbraco.Web.Website.DependencyInjection
|
||||
builder.Services.AddSingleton<HijackedRouteEvaluator>();
|
||||
builder.Services.AddSingleton<IUmbracoRouteValuesFactory, UmbracoRouteValuesFactory>();
|
||||
builder.Services.AddSingleton<IUmbracoRenderingDefaults, UmbracoRenderingDefaults>();
|
||||
builder.Services.AddSingleton<RoutableDocumentFilter>();
|
||||
|
||||
builder.AddDistributedCache();
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using System.Web.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used to check if the current request is for a front-end request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes.
|
||||
/// </remarks>
|
||||
public sealed class RoutableDocumentFilter
|
||||
{
|
||||
public RoutableDocumentFilter(GlobalSettings globalSettings, IIOHelper ioHelper)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_ioHelper = ioHelper;
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, bool> RouteChecks = new ConcurrentDictionary<string, bool>();
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private object _locker = new object();
|
||||
private bool _isInit = false;
|
||||
private int? _routeCount;
|
||||
private HashSet<string> _reservedList;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the request is a document request (i.e. one that the module should handle)
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsDocumentRequest(HttpContextBase httpContext, Uri uri)
|
||||
{
|
||||
var maybeDoc = true;
|
||||
var lpath = uri.AbsolutePath.ToLowerInvariant();
|
||||
|
||||
// handle directory-URLs used for asmx
|
||||
// TODO: legacy - what's the point really?
|
||||
var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase);
|
||||
if (asmxPos >= 0)
|
||||
{
|
||||
// use uri.AbsolutePath, not path, 'cos path has been lowercased
|
||||
httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath
|
||||
uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo
|
||||
uri.Query.TrimStart('?'));
|
||||
maybeDoc = false;
|
||||
}
|
||||
|
||||
// a document request should be
|
||||
// /foo/bar/nil
|
||||
// /foo/bar/nil/
|
||||
// /foo/bar/nil.aspx
|
||||
// where /foo is not a reserved path
|
||||
|
||||
// if the path contains an extension that is not .aspx
|
||||
// then it cannot be a document request
|
||||
var extension = Path.GetExtension(lpath);
|
||||
if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx")
|
||||
maybeDoc = false;
|
||||
|
||||
// at that point, either we have no extension, or it is .aspx
|
||||
|
||||
// if the path is reserved then it cannot be a document request
|
||||
if (maybeDoc && IsReservedPathOrUrl(lpath, httpContext, RouteTable.Routes))
|
||||
maybeDoc = false;
|
||||
|
||||
//NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :)
|
||||
//if (!maybeDoc)
|
||||
//{
|
||||
// Logger.LogWarning<UmbracoModule>("Not a document");
|
||||
//}
|
||||
return maybeDoc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to check.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
internal bool IsReservedPathOrUrl(string url)
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _locker, () =>
|
||||
{
|
||||
// store references to strings to determine changes
|
||||
var reservedPathsCache = _globalSettings.ReservedPaths;
|
||||
var reservedUrlsCache = _globalSettings.ReservedUrls;
|
||||
|
||||
// add URLs and paths to a new list
|
||||
var newReservedList = new HashSet<string>();
|
||||
foreach (var reservedUrlTrimmed in reservedUrlsCache
|
||||
.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => x.Trim().ToLowerInvariant())
|
||||
.Where(x => x.IsNullOrWhiteSpace() == false)
|
||||
.Select(reservedUrl => _ioHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/"))
|
||||
.Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false))
|
||||
{
|
||||
newReservedList.Add(reservedUrlTrimmed);
|
||||
}
|
||||
|
||||
foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)))
|
||||
{
|
||||
newReservedList.Add(reservedPathTrimmed);
|
||||
}
|
||||
|
||||
foreach (var reservedPathTrimmed in NormalizePaths(ReservedPaths))
|
||||
{
|
||||
newReservedList.Add(reservedPathTrimmed);
|
||||
}
|
||||
|
||||
// use the new list from now on
|
||||
return newReservedList;
|
||||
});
|
||||
|
||||
//The URL should be cleaned up before checking:
|
||||
// * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
|
||||
// * We shouldn't be comparing the query at all
|
||||
var pathPart = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant();
|
||||
if (pathPart.Contains(".") == false)
|
||||
{
|
||||
pathPart = pathPart.EnsureEndsWith('/');
|
||||
}
|
||||
|
||||
// return true if URL starts with an element of the reserved list
|
||||
return _reservedList.Any(x => pathPart.InvariantStartsWith(x));
|
||||
}
|
||||
|
||||
private IEnumerable<string> NormalizePaths(IEnumerable<string> paths)
|
||||
{
|
||||
return paths
|
||||
.Select(x => x.Trim().ToLowerInvariant())
|
||||
.Where(x => x.IsNullOrWhiteSpace() == false)
|
||||
.Select(reservedPath => _ioHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/"))
|
||||
.Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current request is reserved based on the route table and
|
||||
/// whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="routes">The route collection to lookup the request in</param>
|
||||
/// <returns></returns>
|
||||
internal bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes)
|
||||
{
|
||||
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
|
||||
if (routes == null) throw new ArgumentNullException(nameof(routes));
|
||||
|
||||
//This is some rudimentary code to check if the route table has changed at runtime, we're basically just keeping a count
|
||||
//of the routes. This isn't fail safe but there's no way to monitor changes to the route table. Else we need to create a hash
|
||||
//of all routes and then recompare but that will be annoying to do on each request and then we might as well just do the whole MVC
|
||||
//route on each request like we were doing before instead of caching the result of GetRouteData.
|
||||
var changed = false;
|
||||
using (routes.GetReadLock())
|
||||
{
|
||||
if (!_routeCount.HasValue || _routeCount.Value != routes.Count)
|
||||
{
|
||||
//the counts are not set or have changed, need to reset
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
using (routes.GetWriteLock())
|
||||
{
|
||||
_routeCount = routes.Count;
|
||||
|
||||
//try clearing each entry
|
||||
foreach(var r in RouteChecks.Keys.ToList())
|
||||
RouteChecks.TryRemove(r, out _);
|
||||
}
|
||||
}
|
||||
|
||||
var absPath = httpContext?.Request?.Url.AbsolutePath;
|
||||
|
||||
if (absPath.IsNullOrWhiteSpace())
|
||||
return false;
|
||||
|
||||
//check if the current request matches a route, if so then it is reserved.
|
||||
var hasRoute = RouteChecks.GetOrAdd(absPath, x => routes.GetRouteData(httpContext) != null);
|
||||
if (hasRoute)
|
||||
return true;
|
||||
|
||||
//continue with the standard ignore routine
|
||||
return IsReservedPathOrUrl(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used internally to track any registered callback paths for Identity providers. If the request path matches
|
||||
/// any of the registered paths, then the module will let the request keep executing
|
||||
/// </summary>
|
||||
internal static readonly ConcurrentHashSet<string> ReservedPaths = new ConcurrentHashSet<string>();
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,6 @@ namespace Umbraco.Web.Runtime
|
||||
return new UmbracoHelper();
|
||||
});
|
||||
|
||||
builder.Services.AddUnique<RoutableDocumentFilter>();
|
||||
|
||||
// configure the container for web
|
||||
//composition.ConfigureForWeb();
|
||||
|
||||
|
||||
@@ -150,7 +150,6 @@
|
||||
<Compile Include="AspNet\AspNetHttpContextAccessor.cs" />
|
||||
<Compile Include="AspNet\AspNetIpResolver.cs" />
|
||||
<Compile Include="AspNet\AspNetPasswordHasher.cs" />
|
||||
<Compile Include="RoutableDocumentFilter.cs" />
|
||||
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
|
||||
<Compile Include="Security\MembershipProviderBase.cs" />
|
||||
<Compile Include="Security\MembershipProviderExtensions.cs" />
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Umbraco.Web
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
private readonly RoutableDocumentFilter _routableDocumentLookup;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
@@ -45,7 +44,6 @@ namespace Umbraco.Web
|
||||
ILogger logger,
|
||||
IPublishedRouter publishedRouter,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
RoutableDocumentFilter routableDocumentLookup,
|
||||
GlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
@@ -53,7 +51,6 @@ namespace Umbraco.Web
|
||||
_logger = logger;
|
||||
_publishedRouter = publishedRouter;
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
_routableDocumentLookup = routableDocumentLookup;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
@@ -157,14 +154,15 @@ namespace Umbraco.Web
|
||||
|
||||
var reason = EnsureRoutableOutcome.IsRoutable;
|
||||
|
||||
// ensure this is a document request
|
||||
if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl))
|
||||
{
|
||||
reason = EnsureRoutableOutcome.NotDocumentRequest;
|
||||
}
|
||||
//// ensure this is a document request
|
||||
//if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl))
|
||||
//{
|
||||
// reason = EnsureRoutableOutcome.NotDocumentRequest;
|
||||
//}
|
||||
|
||||
// ensure the runtime is in the proper state
|
||||
// and deal with needed redirects, etc
|
||||
else if (!EnsureRuntime(httpContext, uri))
|
||||
if (!EnsureRuntime(httpContext, uri))
|
||||
{
|
||||
reason = EnsureRoutableOutcome.NotReady;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user