Fixes #U4-1213, much better support for ignoring routes in route table. Fixes issue with LogHelper. Adds unit tests
for ignoring routes.
This commit is contained in:
@@ -58,20 +58,9 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
get
|
||||
{
|
||||
//first we need to get the parsed base path VirtualPaths in the RouteTable.
|
||||
//NOTE: for performance we would normally store this result in a variable, however since this class is static
|
||||
// we cannot gaurantee that all of the routes will be added to the table before this method is called so
|
||||
// unfortunately, we are stuck resolving these every single time. I don't think the performance will be bad
|
||||
// but it could be better. Need to fix once we fix up all the configuration classes.
|
||||
var parser = new RouteParser(RouteTable.Routes);
|
||||
var reservedFromRouteTable = string.Join(",", parser.ParsedVirtualUrlsFromRouteTable());
|
||||
|
||||
var config = ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths")
|
||||
? ConfigurationManager.AppSettings["umbracoReservedPaths"]
|
||||
: string.Empty;
|
||||
|
||||
//now add the reserved paths
|
||||
return config.IsNullOrWhiteSpace() ? reservedFromRouteTable : config + "," + reservedFromRouteTable;
|
||||
return ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths")
|
||||
? ConfigurationManager.AppSettings["umbracoReservedPaths"]
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,7 +550,29 @@ namespace Umbraco.Core.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <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>
|
||||
public static bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes)
|
||||
{
|
||||
if (httpContext == null) throw new ArgumentNullException("httpContext");
|
||||
if (routes == null) throw new ArgumentNullException("routes");
|
||||
|
||||
//check if the current request matches a route, if so then it is reserved.
|
||||
var route = routes.GetRouteData(httpContext);
|
||||
if (route != null)
|
||||
return true;
|
||||
|
||||
//continue with the standard ignore routine
|
||||
return IsReservedPathOrUrl(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to check.</param>
|
||||
@@ -569,7 +580,7 @@ namespace Umbraco.Core.Configuration
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsReservedPathOrUrl(string url)
|
||||
{
|
||||
{
|
||||
// check if GlobalSettings.ReservedPaths and GlobalSettings.ReservedUrls are unchanged
|
||||
if (!object.ReferenceEquals(_reservedPathsCache, GlobalSettings.ReservedPaths)
|
||||
|| !object.ReferenceEquals(_reservedUrlsCache, GlobalSettings.ReservedUrls))
|
||||
|
||||
@@ -220,7 +220,7 @@ namespace Umbraco.Core.Logging
|
||||
{
|
||||
if (trace != null)
|
||||
{
|
||||
trace.Write(string.Format(generateMessageFormat, formatItems.Select(x => x())));
|
||||
trace.Write(string.Format(generateMessageFormat, formatItems.Select(x => x()).ToArray()));
|
||||
}
|
||||
Debug(typeof(T), generateMessageFormat, formatItems);
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web.Routing;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that is used to parse routes in the route table to determine which route paths to ignore in the GlobalSettings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The parser is not that intelligent, it will not work for very complex URL structures. It will only support simple URL structures that
|
||||
/// have consecutive tokens.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Example routes that will parse:
|
||||
///
|
||||
/// /Test/{controller}/{action}/{id}
|
||||
/// /Test/MyController/{action}/{id}
|
||||
///
|
||||
/// </example>
|
||||
internal class RouteParser
|
||||
{
|
||||
private readonly RouteCollection _routes;
|
||||
private readonly Regex _matchAction = new Regex(@"\{action\}", RegexOptions.Compiled);
|
||||
private readonly Regex _matchController = new Regex(@"\{controller\}", RegexOptions.Compiled);
|
||||
private readonly Regex _matchId = new Regex(@"\{id\}", RegexOptions.Compiled);
|
||||
|
||||
public RouteParser(RouteCollection routes)
|
||||
{
|
||||
_routes = routes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returned all successfully parsed virtual urls from the route collection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<string> ParsedVirtualUrlsFromRouteTable()
|
||||
{
|
||||
return _routes.OfType<Route>()
|
||||
.Select(TryGetBaseVirtualPath)
|
||||
.Where(x => x.Success)
|
||||
.Select(x => x.Result);
|
||||
}
|
||||
|
||||
internal Attempt<string> TryGetBaseVirtualPath(Route route)
|
||||
{
|
||||
var url = route.Url;
|
||||
var controllers = _matchController.Matches(url);
|
||||
var actions = _matchAction.Matches(url);
|
||||
var ids = _matchId.Matches(url);
|
||||
if (controllers.Count > 1)
|
||||
return new Attempt<string>(new InvalidOperationException("The URL cannot be parsed, it must contain zero or one {controller} tokens"));
|
||||
if (actions.Count != 1)
|
||||
return new Attempt<string>(new InvalidOperationException("The URL cannot be parsed, it must contain one {action} tokens"));
|
||||
if (ids.Count != 1)
|
||||
return new Attempt<string>(new InvalidOperationException("The URL cannot be parsed, it must contain one {id} tokens"));
|
||||
|
||||
//now we need to validate that the order
|
||||
if (controllers.Count == 1)
|
||||
{
|
||||
//actions must occur after controllers
|
||||
if (actions[0].Index < controllers[0].Index)
|
||||
return new Attempt<string>(new InvalidOperationException("The {action} token must be placed after the {controller} token"));
|
||||
}
|
||||
//ids must occur after actions
|
||||
if (ids[0].Index < actions[0].Index)
|
||||
return new Attempt<string>(new InvalidOperationException("The {id} token must be placed after the {action} token"));
|
||||
|
||||
//this is all validated, so now we need to return the 'base' virtual path of the route
|
||||
return new Attempt<string>(
|
||||
true,
|
||||
"~/" + url.Substring(0, (controllers.Count == 1 ? controllers[0].Index : actions[0].Index))
|
||||
.TrimEnd('/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,6 @@
|
||||
<Compile Include="ObjectResolution\SingleObjectResolverBase.cs" />
|
||||
<Compile Include="PublishedContentHelper.cs" />
|
||||
<Compile Include="RenderingEngine.cs" />
|
||||
<Compile Include="RouteParser.cs" />
|
||||
<Compile Include="TypeExtensions.cs" />
|
||||
<Compile Include="ReadLock.cs" />
|
||||
<Compile Include="TypeFinder.cs" />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Configuration;
|
||||
using System.Web.Routing;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Tests
|
||||
{
|
||||
@@ -53,5 +55,42 @@ namespace Umbraco.Tests
|
||||
{
|
||||
Assert.IsFalse(Umbraco.Core.Configuration.GlobalSettings.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
|
||||
ConfigurationManager.AppSettings.Set("umbracoReservedPaths", "");
|
||||
ConfigurationManager.AppSettings.Set("umbracoReservedUrls", "");
|
||||
|
||||
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,
|
||||
Umbraco.Core.Configuration.GlobalSettings.IsReservedPathOrUrl(url, context.HttpContext, routes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Routing;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class RouteParserTests
|
||||
{
|
||||
/// <summary>
|
||||
/// used for testing
|
||||
/// </summary>
|
||||
private class MyRoute : RouteBase
|
||||
{
|
||||
private readonly string _url;
|
||||
|
||||
public MyRoute(string url)
|
||||
{
|
||||
_url = url;
|
||||
}
|
||||
|
||||
public override RouteData GetRouteData(HttpContextBase httpContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Successfully_Parsed_Urls()
|
||||
{
|
||||
var urls = new RouteBase[]
|
||||
{
|
||||
new Route("Umbraco/RenderMvc/{action}/{id}", null),
|
||||
new Route("Another/Test/{controller}/{action}/{id}", null),
|
||||
new MyRoute("Umbraco/RenderMvc/{action}/{id}"), //wont match because its not a route object
|
||||
new Route("Another/Test/{id}/{controller}/{action}", null) //invalid
|
||||
};
|
||||
var valid = new string[]
|
||||
{
|
||||
"~/Umbraco/RenderMvc",
|
||||
"~/Another/Test"
|
||||
};
|
||||
|
||||
var collection = new RouteCollection();
|
||||
foreach (var u in urls)
|
||||
{
|
||||
collection.Add(u);
|
||||
}
|
||||
var parser = new RouteParser(collection);
|
||||
|
||||
var result = parser.ParsedVirtualUrlsFromRouteTable();
|
||||
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.IsTrue(result.ContainsAll(valid));
|
||||
}
|
||||
|
||||
[TestCase("Umbraco/RenderMvc/{action}/{id}", "~/Umbraco/RenderMvc")]
|
||||
[TestCase("Install/PackageInstaller/{action}/{id}", "~/Install/PackageInstaller")]
|
||||
[TestCase("Test/{controller}/{action}/{id}", "~/Test")]
|
||||
[TestCase("Another/Test/{controller}/{action}/{id}", "~/Another/Test")]
|
||||
public void Successful_Virtual_Path(string url, string result)
|
||||
{
|
||||
var route = new Route(url, null);
|
||||
var parser = new RouteParser(new RouteCollection());
|
||||
var attempt = parser.TryGetBaseVirtualPath(route);
|
||||
if (!attempt.Success && attempt.Error != null)
|
||||
throw attempt.Error; //throw this so we can see the error in the test output
|
||||
|
||||
Assert.IsTrue(attempt.Success);
|
||||
Assert.AreEqual(result, attempt.Result);
|
||||
}
|
||||
|
||||
[TestCase("Umbraco/RenderMvc/{action}/{controller}/{id}")]
|
||||
[TestCase("Install/PackageInstaller/{id}/{action}")]
|
||||
[TestCase("Test/{controller}/{id}/{action}")]
|
||||
[TestCase("Another/Test/{id}/{controller}/{action}")]
|
||||
public void Non_Parsable_Virtual_Path(string url)
|
||||
{
|
||||
var route = new Route(url, null);
|
||||
var parser = new RouteParser(new RouteCollection());
|
||||
var attempt = parser.TryGetBaseVirtualPath(route);
|
||||
Assert.IsFalse(attempt.Success);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,6 @@
|
||||
<Compile Include="ObjectExtensionsTests.cs" />
|
||||
<Compile Include="ContentStores\PublishContentStoreTests.cs" />
|
||||
<Compile Include="DataTypeFactoryTests.cs" />
|
||||
<Compile Include="RouteParserTests.cs" />
|
||||
<Compile Include="Routing\LookupByNiceUrlWithDomainsTests.cs" />
|
||||
<Compile Include="Routing\NiceUrlsProviderWithDomainsTests.cs" />
|
||||
<Compile Include="Routing\uQueryGetNodeIdByUrlTests.cs" />
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Umbraco.Web
|
||||
// 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 && GlobalSettings.IsReservedPathOrUrl(lpath))
|
||||
if (maybeDoc && GlobalSettings.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 :)
|
||||
|
||||
Reference in New Issue
Block a user