aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Model/XmlRouteStore.cs
blob: fce193ea28e2488eabb010c4e978e65c9ede2070 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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, CancellationToken cancellation)
        {
            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, 8192, MemoryUtil.Shared, cancellation);
            }

            //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["privilege"]?.Value;
                _ = privAtr ?? throw new XmlException("Missing required attribute 'privilege' in route element");

                //Try to get the priv level enum value
                if (ulong.TryParse(privAtr, out ulong priv))
                {
                    route.Privilege = 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);
            }
        }
    }
}