diff options
author | vnugent <public@vaughnnugent.com> | 2023-05-29 17:35:08 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-05-29 17:35:08 -0400 |
commit | 765d3d328af49f92f1d0b296bfba2d7791e0cdf5 (patch) | |
tree | 4cdea3095b53f06508dbf05f4181c53e2844fc48 /plugins/VNLib.Plugins.Essentials.Content.Routing | |
parent | 31220eaf6583c28f2df5070c3c8841a02a17cdbe (diff) |
XML file backed content routing, no more db required
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.Content.Routing')
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs | 41 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs | 62 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/DbRouteStore.cs (renamed from plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs) | 31 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs | 37 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs | 161 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs | 14 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs | 120 | ||||
-rw-r--r-- | plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml | 44 |
8 files changed, 445 insertions, 65 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs new file mode 100644 index 0000000..39af773 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/IRouteStore.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: IRouteStore.cs +* +* IRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.Content.Routing is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System.Threading.Tasks; +using System.Collections.Generic; + +using VNLib.Plugins.Essentials.Content.Routing.Model; + +namespace VNLib.Plugins.Essentials.Content.Routing +{ + internal interface IRouteStore + { + /// <summary> + /// Loads all routes from the backing storage element asynchronously + /// </summary> + /// <param name="routes">The collection to store loaded routes to</param> + /// <returns>A task that completes when the routes are added to the collection</returns> + Task GetAllRoutesAsync(ICollection<Route> routes); + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs new file mode 100644 index 0000000..d859551 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/ManagedRouteStore.cs @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: ManagedRouteStore.cs +* +* ManagedRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.Content.Routing is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System.Threading.Tasks; +using System.Collections.Generic; + +using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Extensions.Loading.Sql; +using VNLib.Plugins.Essentials.Content.Routing.Model; + +namespace VNLib.Plugins.Essentials.Content.Routing +{ + [ConfigurationName("store")] + internal sealed class ManagedRouteStore : IRouteStore + { + private readonly IRouteStore _routeStore; + + public ManagedRouteStore(PluginBase plugin, IConfigScope config) + { + //Load managed store, see if we're using the xml file or the database + if(config.ContainsKey("route_file")) + { + //Load xml route store + _routeStore = plugin.GetOrCreateSingleton<XmlRouteStore>(); + } + else + { + //Load the database backed store + _routeStore = plugin.GetOrCreateSingleton<DbRouteStore>(); + + //Ensure the database is created + _ = plugin.ObserveWork(() => plugin.EnsureDbCreatedAsync<RoutingContext>(plugin), 500); + } + } + + public Task GetAllRoutesAsync(ICollection<Route> routes) + { + return _routeStore.GetAllRoutesAsync(routes); + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/DbRouteStore.cs index e623228..0c2fca6 100644 --- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/RouteStore.cs +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/DbRouteStore.cs @@ -1,11 +1,11 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Content.Routing -* File: RouteStore.cs +* File: DbRouteStore.cs * -* RouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger +* DbRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify @@ -24,24 +24,37 @@ using System; using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using VNLib.Plugins.Extensions.Data; +using VNLib.Plugins.Extensions.Loading.Sql; namespace VNLib.Plugins.Essentials.Content.Routing.Model { - internal class RouteStore : DbStore<Route> + internal class DbRouteStore : DbStore<Route>, IRouteStore { private readonly DbContextOptions Options; - public RouteStore(DbContextOptions options) + public DbRouteStore(PluginBase plugin) { - Options = options; + //Load the db context options + Options = plugin.GetContextOptions(); } + ///<inheritdoc/> + public Task GetAllRoutesAsync(ICollection<Route> routes) + { + //Get all routes as a single page from the database + return GetPageAsync(routes, 0, int.MaxValue); + } + + ///<inheritdoc/> public override string RecordIdBuilder => Guid.NewGuid().ToString("N"); + ///<inheritdoc/> protected override IQueryable<Route> GetCollectionQueryBuilder(TransactionalDbContext context, params string[] constraints) { string hostname = constraints[0]; @@ -50,6 +63,7 @@ namespace VNLib.Plugins.Essentials.Content.Routing.Model select route; } + ///<inheritdoc/> protected override IQueryable<Route> GetSingleQueryBuilder(TransactionalDbContext context, params string[] constraints) { string id = constraints[0]; @@ -58,11 +72,14 @@ namespace VNLib.Plugins.Essentials.Content.Routing.Model select route; } + ///<inheritdoc/> public override TransactionalDbContext NewContext() => new RoutingContext(Options); + ///<inheritdoc/> protected override void OnRecordUpdate(Route newRecord, Route currentRecord) { - throw new NotImplementedException(); + throw new NotSupportedException(); } + } } diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs index c1531f7..acceb0c 100644 --- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/Route.cs @@ -35,38 +35,53 @@ namespace VNLib.Plugins.Essentials.Content.Routing.Model [Index(nameof(Id), IsUnique = true)] internal class Route : DbModelBase { + public const FpRoutine RewriteRoutine = (FpRoutine)50; + [Key] public override string Id { get; set; } public override DateTime Created { get; set; } public override DateTime LastModified { get; set; } public string Hostname { get; set; } + public string MatchPath { get; set; } + [Column("Privilage")] public long _privilage { get => (long)Privilage; set => Privilage = (ulong)value; } + [NotMapped] public ulong Privilage { get; set; } - public string Alternate { get; set; } - public FpRoutine Routine { get; set; } + public string? Alternate { get; set; } = string.Empty; + + public FpRoutine Routine { get; set; } + + public string? RewriteSearch { get; set; } /// <summary> - /// The processing arguments that match the route + /// Creates the <see cref="FileProcessArgs"/> to return to the processor + /// for the current rule, which may include rewriting the url. /// </summary> - [NotMapped] - public FileProcessArgs MatchArgs + /// <param name="entity">The connection to get the args for</param> + /// <returns>The <see cref="FileProcessArgs"/> for the connection</returns> + public FileProcessArgs GetArgs(HttpEntity entity) { - get + //Check for rewrite routine + if (Routine == RewriteRoutine) + { + //Rewrite the request url and return the args, processor will clean and parse url + string rewritten = entity.Server.Path.Replace(RewriteSearch!, Alternate!, StringComparison.OrdinalIgnoreCase); + + //Set to rewrite args + return new FileProcessArgs(FpRoutine.ServeOther, rewritten); + } + else { - return new FileProcessArgs() - { - Alternate = this.Alternate, - Routine = (FpRoutine) Routine - }; + return new FileProcessArgs(Routine, Alternate!); } } } diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs new file mode 100644 index 0000000..5420996 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Essentials.Content.Routing +* File: XmlRouteStore.cs +* +* XmlRouteStore.cs is part of VNLib.Plugins.Essentials.Content.Routing which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Essentials.Content.Routing is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Essentials.Content.Routing is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; +using System.IO; +using System.Xml; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +using VNLib.Utils.IO; +using VNLib.Utils.Memory; +using VNLib.Utils.Logging; +using VNLib.Utils.Extensions; +using VNLib.Plugins.Extensions.Loading; + +namespace VNLib.Plugins.Essentials.Content.Routing.Model +{ + [ConfigurationName("store")] + internal sealed class XmlRouteStore : IRouteStore + { + private readonly string _routeFile; + + public XmlRouteStore(PluginBase plugin, IConfigScope config) + { + //Get the route file path + _routeFile = config["route_file"].GetString() ?? throw new KeyNotFoundException("Missing required key 'route_file' in 'route_store' configuration element"); + + //Make sure the file exists + if (!FileOperations.FileExists(_routeFile)) + { + throw new FileNotFoundException("Missing required route xml file", _routeFile); + } + + plugin.Log.Debug("Loading routes from {0}", _routeFile); + } + + ///<inheritdoc/> + public async Task GetAllRoutesAsync(ICollection<Route> routes) + { + using VnMemoryStream memStream = new(); + + //Load the route file + await using (FileStream routeFile = new(_routeFile, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + //Read the route file into memory + await routeFile.CopyToAsync(memStream, 4096, MemoryUtil.Shared, CancellationToken.None); + } + + //Rewind the memory stream + memStream.Seek(0, SeekOrigin.Begin); + + //Parse elements into routes + ParseElements(memStream, routes); + } + + private static void ParseElements(VnMemoryStream ms, ICollection<Route> routes) + { + //Read contents into xml doc for reading + XmlDocument xmlDoc = new(); + xmlDoc.Load(ms); + + //Get route elements + XmlNodeList? routeElements = xmlDoc.SelectNodes("routes/route"); + + //If no route elements, exit + if (routeElements == null) + { + return; + } + + foreach (XmlNode routeEl in routeElements) + { + Route route = new(); + + //See if route is disabled + string? disabledAtr = routeEl.Attributes["disabled"]?.Value; + //If disabled, skip route + if (disabledAtr != null) + { + continue; + } + + //Get the route routine value + string? routineAtr = routeEl.Attributes["routine"]?.Value; + _ = routineAtr ?? throw new XmlException("Missing required attribute 'routine' in route element"); + + //Try to get the routime enum value + if (uint.TryParse(routineAtr, out uint r)) + { + route.Routine = (FpRoutine)r; + } + else + { + throw new XmlException("The value of the 'routine' attribute is not a valid FpRoutine enum value"); + } + + //read priv level attribute + string? privAtr = routeEl.Attributes["privilage"]?.Value; + _ = privAtr ?? throw new XmlException("Missing required attribute 'priv' in route element"); + + //Try to get the priv level enum value + if (ulong.TryParse(privAtr, out ulong priv)) + { + route.Privilage = priv; + } + else + { + throw new XmlException("The value of the 'priv' attribute is not a valid unsigned 64-bit integer"); + } + + //Get hostname element value + string? hostEl = routeEl["hostname"]?.InnerText; + route.Hostname = hostEl ?? throw new XmlException("Missing required element 'hostname' in route element"); + + //Get the path element value + string? pathEl = routeEl["path"]?.InnerText; + route.MatchPath = pathEl ?? throw new XmlException("Missing required element 'path' in route element"); + + //Get the optional alternate path element value + route.Alternate = routeEl["alternate"]?.InnerText; + + //Check for rewrite routine, if rewrite, get rewrite and replace elements + if (route.Routine == Route.RewriteRoutine) + { + //Get the rewrite element value + string? rewriteEl = routeEl["rewrite"]?.InnerText; + route.RewriteSearch = rewriteEl ?? throw new XmlException("Missing required element 'rewrite' in route element"); + + //Get the rewrite element value + string? replaceEl = routeEl["replace"]?.InnerText; + route.Alternate = replaceEl ?? throw new XmlException("Missing required element 'replace' in route element"); + } + + //add route to the collection + routes.Add(route); + } + } + } +} diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs index e71ee6b..ef7836f 100644 --- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/PageRouterEntry.cs @@ -23,14 +23,11 @@ */ using System; -using System.Threading.Tasks; using System.ComponentModel.Design; using VNLib.Utils.Logging; using VNLib.Plugins.Attributes; using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Extensions.Loading.Sql; -using VNLib.Plugins.Essentials.Content.Routing.Model; namespace VNLib.Plugins.Essentials.Content.Routing { @@ -52,9 +49,6 @@ namespace VNLib.Plugins.Essentials.Content.Routing //Init router PageRouter = this.GetOrCreateSingleton<Router>(); - //Schedule the db creation - _ = this.ObserveWork(OnDbCreationAsync, 500); - Log.Information("Plugin loaded"); } @@ -70,12 +64,6 @@ namespace VNLib.Plugins.Essentials.Content.Routing PageRouter?.ResetRoutes(); Log.Information("Routing table reset"); } - } - - private async Task OnDbCreationAsync() - { - //Create the router - await this.EnsureDbCreatedAsync<RoutingContext>(null); - } + } } } diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs index fdf5f59..3d3a1a6 100644 --- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs @@ -30,40 +30,40 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Collections.ObjectModel; +using VNLib.Utils.Logging; +using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Accounts; using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Extensions.Loading.Sql; using VNLib.Plugins.Essentials.Content.Routing.Model; -using static VNLib.Plugins.Essentials.Accounts.AccountUtil; namespace VNLib.Plugins.Essentials.Content.Routing { - [ConfigurationName("page_router", Required = false)] + internal class Router : IPageRouter { private static readonly RouteComparer Comparer = new(); - private readonly RouteStore Store; + private readonly IRouteStore Store; + private readonly ILogProvider Logger; private readonly ConcurrentDictionary<IWebProcessor, Task<ReadOnlyCollection<Route>>> RouteTable; public Router(PluginBase plugin) { - Store = new(plugin.GetContextOptions()); + Store = plugin.GetOrCreateSingleton<ManagedRouteStore>(); + Logger = plugin.Log; RouteTable = new(); } - public Router(PluginBase plugin, IConfigScope config) - { - Store = new(plugin.GetContextOptions()); - RouteTable = new(); - } + public Router(PluginBase plugin, IConfigScope config):this(plugin) + { } ///<inheritdoc/> public async ValueTask<FileProcessArgs> RouteAsync(HttpEntity entity) { - ulong privilage = READ_MSK; + //Default to read-only privilages + ulong privilage = AccountUtil.READ_MSK; //Only select privilages for logged-in users, this is a medium security check since we may not have all data available if (entity.Session.IsSet && entity.IsClientAuthorized(AuthorzationCheckLevel.Medium)) @@ -73,8 +73,12 @@ namespace VNLib.Plugins.Essentials.Content.Routing //Get the routing table for the current host ReadOnlyCollection<Route> routes = await RouteTable.GetOrAdd(entity.RequestedRoot, LoadRoutesAsync); + //Find the proper routine for the connection - return FindArgs(routes, entity.RequestedRoot.Hostname, entity.Server.Path, privilage); + Route? selected = SelectBestRoute(routes, entity.RequestedRoot.Hostname, entity.Server.Path, privilage); + + //Get the arguments for the selected route, if not found allow the connection to continue + return selected?.GetArgs(entity) ?? FileProcessArgs.Continue; } /// <summary> @@ -82,11 +86,16 @@ namespace VNLib.Plugins.Essentials.Content.Routing /// </summary> public void ResetRoutes() => RouteTable.Clear(); + private async Task<ReadOnlyCollection<Route>> LoadRoutesAsync(IWebProcessor root) { List<Route> collection = new(); - //Load all routes - _ = await Store.GetPageAsync(collection, 0, int.MaxValue); + + //Load all routes from the backing store and filter them + await Store.GetAllRoutesAsync(collection); + + Logger.Debug("Found {r} routes in store", collection.Count); + //Select only exact match routes, or wildcard routes return (from r in collection where r.Hostname.EndsWith(root.Hostname, StringComparison.OrdinalIgnoreCase) || r.Hostname == "*" @@ -97,37 +106,67 @@ namespace VNLib.Plugins.Essentials.Content.Routing .AsReadOnly(); } - - private static FileProcessArgs FindArgs(ReadOnlyCollection<Route> routes, string hostname, string path, ulong privilages) + /// <summary> + /// Selects the best route for a given hostname, path, and privilage level and returns it + /// if one could be found + /// </summary> + /// <param name="routes">The routes collection to read</param> + /// <param name="hostname">The connection hostname to filter routes for</param> + /// <param name="path">The connection url path to filter routes for</param> + /// <param name="privilages">The calculated privialges of the connection</param> + /// <returns>The best route match for the connection if one is found, null otherwise</returns> + private static Route? SelectBestRoute(ReadOnlyCollection<Route> routes, string hostname, string path, ulong privilages) { //Rent an array to sort routes for the current user Route[] matchArray = ArrayPool<Route>.Shared.Rent(routes.Count); int count = 0; + //Search for routes that match - for(int i = 0; i < routes.Count; i++) + for (int i = 0; i < routes.Count; i++) { - if(Matches(routes[i], hostname, path, privilages)) + if (FastMatch(routes[i], hostname, path, privilages)) { //Add to sort array matchArray[count++] = routes[i]; } } + //If no matches are found, return continue routine if (count == 0) { //Return the array to the pool ArrayPool<Route>.Shared.Return(matchArray); - return FileProcessArgs.Continue; + return null; + } + + //If only one match is found, return it + if (count == 1) + { + //Return the array to the pool + ArrayPool<Route>.Shared.Return(matchArray); + return matchArray[0]; + } + else + { + //Get sorting span for matches + Span<Route> found = matchArray.AsSpan(0, count); + + /* + * Sortining elements using the static comparer, to find the best match + * out of all matching routes. + * + * The comparer will put the most specific routes at the end of the array + */ + found.Sort(Comparer); + + //Select the last element + Route selected = found[^1]; + + //Return array to pool + ArrayPool<Route>.Shared.Return(matchArray); + + return selected; } - //Get sorting span for matches - Span<Route> found = matchArray.AsSpan(0, count); - //Sort the found rules - found.Sort(Comparer); - //Select the last element - Route selected = found[^1]; - //Return array to pool - ArrayPool<Route>.Shared.Return(matchArray); - return selected.MatchArgs; } /// <summary> @@ -139,25 +178,38 @@ namespace VNLib.Plugins.Essentials.Content.Routing /// <param name="path">The resource path to test</param> /// <param name="privilages">The privialge level to search for</param> /// <returns>True if the route can be matched to the resource and the privialge level</returns> - private static bool Matches(Route route, ReadOnlySpan<char> hostname, ReadOnlySpan<char> path, ulong privilages) + private static bool FastMatch(Route route, ReadOnlySpan<char> hostname, ReadOnlySpan<char> path, ulong privilages) { //Get span of hostname to stop string heap allocations during comparisons ReadOnlySpan<char> routineHost = route.Hostname; ReadOnlySpan<char> routinePath = route.MatchPath; - //Test if hostname hostname matches exactly (may be wildcard) or hostname begins with a wildcard and ends with the request hostname - bool hostMatch = routineHost.SequenceEqual(hostname) || (routineHost.Length > 1 && routineHost[0] == '*' && hostname.EndsWith(routineHost[1..])); + + //Test if hostname matches + bool hostMatch = + //Wildcard routine only, matches all hostnames + (routineHost.Length == 1 && routineHost[0] == '*') + //Exact hostname match + || routineHost.SequenceEqual(hostname) + //wildcard hostname match with trailing + || (routineHost.Length > 1 && routineHost[0] == '*' && hostname.EndsWith(routineHost[1..], StringComparison.OrdinalIgnoreCase)); + if (!hostMatch) { return false; } - //Test if path is a wildcard, matches exactly, or if the path is a wildcard path, that the begining of the reqest path matches the routine path - bool pathMatch = routinePath == "*" || routinePath.SequenceEqual(path) || (routinePath.Length > 1 && routinePath[^1] == '*' && path.StartsWith(routinePath[..^1])); + + //Test if path is a wildcard, matches exactly, or if the path is a wildcard path, that the begining of the request path matches the routine path + bool pathMatch = routinePath == "*" + || routinePath.Equals(path, StringComparison.OrdinalIgnoreCase) + || (routinePath.Length > 1 && routinePath[^1] == '*' && path.StartsWith(routinePath[..^1], StringComparison.OrdinalIgnoreCase)); + if (!pathMatch) { return false; } + //Test if the level and group privilages match for the current routine - return (privilages & LEVEL_MSK) >= (route.Privilage & LEVEL_MSK) && (route.Privilage & GROUP_MSK) == (privilages & GROUP_MSK); + return (privilages & AccountUtil.LEVEL_MSK) >= (route.Privilage & AccountUtil.LEVEL_MSK) && (route.Privilage & AccountUtil.GROUP_MSK) == (privilages & AccountUtil.GROUP_MSK); } } } diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml new file mode 100644 index 0000000..3c87aa7 --- /dev/null +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/sample.routes.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8" ?> + +<!--Routes container element holds a collection of route elements--> +<routes> + <!-- + Example route configuration for a single page app + where the tree looks like this: + / (index.html) + /assets (assets directory) (css and js files) + + Wildcard hosts match all hosts that do not have rules with more specific hosts + --> + + <!-- + Allow assets directory to pass through for all requests, using the Continue routine (1) + + Because this route has a more specific path than the catch all route + it will be processed first + --> + <route routine="1" privilage="0"> + + <!--Wildcard host--> + <hostname>*</hostname> + + <!--All paths that start with /assets/ will be matched--> + <path>/assets/*</path> + </route> + + <!--Overwrite all other requests to the index file (catch all) using the ServeOther routine (4)--> + <route routine="4" privilage="0"> + + <!--Wildcard hostname--> + <hostname>*</hostname> + + <!--Declares that all files after / will be matched by this rule--> + <path>/*</path> + + <!--Return to the root path, lets the file processor handle extension searching--> + <alternate>/</alternate> + </route> + + <!--All routes that do not match will be allowed, this is only / since it does not have a matching rule--> + +</routes>
\ No newline at end of file |