aboutsummaryrefslogtreecommitdiff
path: root/apps/VNLib.WebServer/src/VirtualHosts
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-08-04 17:14:10 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-08-04 17:14:10 -0400
commit0419f315e5689e043f311203ab8e61f69f1ee1d6 (patch)
treef077b9d32d787bdef6e2cded203bc459a23dae7b /apps/VNLib.WebServer/src/VirtualHosts
parent7be5d6648e633ba46a270ca5784de6f4a5a4e0a9 (diff)
Squashed commit of the following:
commit bf6085a67a25c0242e3f170c3e617a08498d9ad0 Author: vnugent <public@vaughnnugent.com> Date: Sun Aug 4 16:58:09 2024 -0400 fix compression source tree and source package commit 4eb2cf913495e8a7c8c9ad3fceb3bff2a1b2a072 Author: vnugent <public@vaughnnugent.com> Date: Sun Aug 4 15:49:27 2024 -0400 push to codeberg commit 0b69bc760f87efab73ca6efb454b30a3393be269 Author: vnugent <public@vaughnnugent.com> Date: Sun Aug 4 15:36:26 2024 -0400 consolidate log config commit b8841c2218133bb8692e30cee0cfc719bfa5e9a0 Author: vnugent <public@vaughnnugent.com> Date: Thu Aug 1 21:30:59 2024 -0400 update compression copyright data and add package.json commit 08020ccace1474e27702ad6575259e799ca56b63 Merge: 904560a ff1765d Author: vnugent <public@vaughnnugent.com> Date: Thu Aug 1 21:26:47 2024 -0400 Merge branch 'develop' into slurp-webserver commit ff1765d3aa4d98fd223c47d16fca8e3e13a4d894 Author: vnugent <public@vaughnnugent.com> Date: Thu Aug 1 21:26:05 2024 -0400 set trasnport manager setters private for debug commit 904560a7b5eafd7580fb0a03e778d1751e72a503 Author: vnugent <public@vaughnnugent.com> Date: Thu Aug 1 21:13:04 2024 -0400 build(app): swallow vnlib.webserver into core & build updates commit 6af95e61212611908d39235222474d4038e10fcd Author: vnugent <public@vaughnnugent.com> Date: Wed Jul 31 15:14:07 2024 -0400 ci: clean up heapapi header & better formalize taskfiles commit 52965ce8bb0b06f59b07c7f6b5a9de6bfbc22b40 Author: vnugent <public@vaughnnugent.com> Date: Mon Jul 29 12:26:32 2024 -0400 move dev-init to module level for vnbuild module init commit e66b75642f654f623a3115bd3586d567d1554726 Merge: a4dacd2 7be5d66 Author: vnugent <public@vaughnnugent.com> Date: Sun Jul 28 19:16:13 2024 -0400 Merge branch 'master' into develop commit a4dacd2909426bf628c1eee1253cc5c8a01e2691 Author: vnugent <public@vaughnnugent.com> Date: Sat Jul 27 22:41:04 2024 -0400 package updates commit f836e09981866f5c9f2ae46990d11b186a7d12bb Author: vnugent <public@vaughnnugent.com> Date: Wed Jul 24 19:15:54 2024 -0400 chore: Remove argon2 docs & optional tcp resuse commit b9b892ab2143b0ab92e4dcf0a8b043c5c6c17271 Author: vnugent <public@vaughnnugent.com> Date: Sun Jul 21 20:57:01 2024 -0400 fix spelling Enqueue and deprecate mispelled version commit 21ffa816f18be4b765ad740ed5d93346ec3b1fda Author: vnugent <public@vaughnnugent.com> Date: Sat Jul 20 19:44:31 2024 -0400 static arugment list parsing functions commit 85cd6793818a3edd0a963bb4829a960ee6b0e022 Author: vnugent <public@vaughnnugent.com> Date: Mon Jul 15 18:58:06 2024 -0400 chore: Just some minor checks and adjustments commit abfb5761ee381b7e1e5342a5525ceca8c8fd81dd Author: vnugent <public@vaughnnugent.com> Date: Thu Jul 4 23:57:37 2024 -0400 analyzer pass commit 4a96dbb924f2b5bf80293e4054f221efe67151dd Author: vnugent <public@vaughnnugent.com> Date: Thu Jul 4 22:45:28 2024 -0400 package updates commit 38ad7d923fa8d9e463d4aaa8e35f021086a03f2d Author: vnugent <public@vaughnnugent.com> Date: Thu Jul 4 16:20:48 2024 -0400 mimalloc merge upstream upgrades commit 981ba286e4793de95bf65e6588313411344c4d53 Author: vnugent <public@vaughnnugent.com> Date: Thu Jul 4 16:04:03 2024 -0400 refactor: Refactor extensions with perf updates commit 6b8c67888731f7dd210acdb2b1160cdbdbe30d47 Author: vnugent <public@vaughnnugent.com> Date: Fri Jun 28 15:48:22 2024 -0400 refactor: Update service stack to reflect new loading patterns commit 12391e9a207b60b41a074600fc2373ad3eb1c3ab Author: vnugent <public@vaughnnugent.com> Date: Wed Jun 26 21:01:15 2024 -0400 feat(server): Server arch update, Memory struct access commit 92e182ceaf843f8d859d38faa8b2c0ff53207ff6 Author: vnugent <public@vaughnnugent.com> Date: Fri Jun 21 16:02:34 2024 -0400 feat: Multi transport listeners commit ee3620b8168a42c8e571e853c751ad5999a9b907 Author: vnugent <public@vaughnnugent.com> Date: Tue Jun 18 21:17:28 2024 -0400 feat: Add file path caching support commit ff0926be56fc6eafdce36411847d73bf4ce9f183 Author: vnugent <public@vaughnnugent.com> Date: Sun Jun 16 13:08:31 2024 -0400 feat: Allow multiple plugin loading directories commit 07ddf6738d32127926d07b1366e56d2a2308b53b Author: vnugent <public@vaughnnugent.com> Date: Sun Jun 16 01:12:07 2024 -0400 perf: Absolutely yuge perf boosts commit ff15c05a9c3e632c39f3889820fb7d889342b452 Author: vnugent <public@vaughnnugent.com> Date: Fri Jun 14 14:16:24 2024 -0400 fix: Improper request buffer property assignment commit 7d2987f1d4048c30808a85798e32c99747f6cfe3 Author: vnugent <public@vaughnnugent.com> Date: Thu Jun 13 21:57:34 2024 -0400 perf: Async pre-buffer to avoid sync buffer commit 75c1d0cbf9a5a7856c544671a45f1b4312ffe7ce Author: vnugent <public@vaughnnugent.com> Date: Tue Jun 11 22:11:45 2024 -0400 feat: Add a default site adapater and interceptors commit a7c739b7db9a17622cee751fe0e8a10e4b84b48b Author: vnugent <public@vaughnnugent.com> Date: Sun Jun 9 13:05:12 2024 -0400 chore: Package updated commit b4b506a4b6c7c1e90b5b0980e4cfe0460e7546a2 Author: vnugent <public@vaughnnugent.com> Date: Sat Jun 8 21:54:52 2024 -0400 some minor touchups commit 2160510fcc22a8574b0090fd91ca29072f45ab59 Author: vnugent <public@vaughnnugent.com> Date: Fri May 31 15:12:20 2024 -0400 refactor: Immutable tcp listeners commit 51cb4eb93e4f1b4c47d35b105e72af1fe771abcc Author: vnugent <public@vaughnnugent.com> Date: Thu May 30 17:31:16 2024 -0400 refactor: minor non-breaking changes to VNEncoding commit 768ddc1eb949266d693f96c67d734e881bd59374 Merge: 9a835fe 1b590c2 Author: vnugent <public@vaughnnugent.com> Date: Wed May 22 17:50:57 2024 -0400 Merge branch 'main' into develop commit 9a835fe12c9586ab8dd44d7c96fef4a2d6017e4b Author: vnugent <public@vaughnnugent.com> Date: Fri May 17 18:27:03 2024 -0400 chore: Update mimmaloc v2.1.6, update fPIC & cleanup commit 3b7004b88acfc7f7baa3a8857a5a2f7cf3dd560e Author: vnugent <public@vaughnnugent.com> Date: Fri May 17 16:03:28 2024 -0400 feat: Added ReadFileDataAsync function commit 9a964795757bf0da4dd7fcab15ad304f4ea3fdf1 Author: vnugent <public@vaughnnugent.com> Date: Wed May 15 21:57:39 2024 -0400 refactor: Harden some argon2 password hashing commit 4035c838c1508af0aa7e767a97431a692958ce1c Author: vnugent <public@vaughnnugent.com> Date: Sun May 12 16:55:32 2024 -0400 perf: Utils + http perf mods commit f4f0d4f74250257991c57bfae74c4852c7e1ae46 Author: vnugent <public@vaughnnugent.com> Date: Thu May 2 15:22:53 2024 -0400 feat: Buff middleware handlers | | Added implicit support for middleware post processing of files before the filehandler closes the connection. Also cleaned up some project file stuff commit f0b7dca107659df1d7d4631fdbd2aae9d716d053 Merge: 8c4a45e 107b058 Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 20 12:24:05 2024 -0400 Merge branch 'main' into develop commit 8c4a45e384accf92b1b6d748530e8d46f7de40d6 Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 20 11:10:30 2024 -0400 refactor: Overhaul C libraries and fix builds commit 42ff77080d10b0fc9fecbbc46141e8e23a1d066a Author: vnugent <public@vaughnnugent.com> Date: Sat Apr 20 00:45:57 2024 -0400 fix!: Middlware array, multiple cookie set, and cookie check commit 97e82b9d66f387f9e6d21d88ddc7a8ab8693149c Merge: 4ca5791 e07537a Author: vnugent <public@vaughnnugent.com> Date: Tue Apr 2 13:34:22 2024 -0400 Merge branch 'main' into develop commit 4ca5791ed67b9834bdbd010206b30373e4705e9b Author: vnugent <public@vaughnnugent.com> Date: Tue Apr 2 13:32:12 2024 -0400 fix: Missed ! on null pointer check commit 9b4036377c52200c6488c98180d69a0e63321f97 Author: vnugent <public@vaughnnugent.com> Date: Tue Apr 2 13:22:29 2024 -0400 fix: Fix _In_ macro for compression public api commit 53a7b4b5c5b67b4a4e06e1d9098cac4bcd6afd7c Merge: 448a93b 21130c8 Author: vnugent <public@vaughnnugent.com> Date: Sun Mar 31 17:01:15 2024 -0400 Merge branch 'main' into develop commit 448a93bb1d18d032087afe2476ffccb98634a54c Author: vnugent <public@vaughnnugent.com> Date: Sun Mar 31 16:56:51 2024 -0400 ci: fix third-party dir cleanup commit 9afed1427472da1ea13079f98dbe27339e55ee7d Author: vnugent <public@vaughnnugent.com> Date: Sun Mar 31 16:43:15 2024 -0400 perf: Deprecate unsafememoryhandle span extensions commit 3ff90da4f02af47ea6d233fdd4445337ebe36452 Author: vnugent <public@vaughnnugent.com> Date: Sat Mar 30 21:36:18 2024 -0400 refactor: Updates, advanced tracing, http optimizations commit 8d6b79b5ae309b36f265ba81529bcef8bfcd7414 Merge: 6c1667b 5585915 Author: vnugent <public@vaughnnugent.com> Date: Sun Mar 24 21:01:31 2024 -0400 Merge branch 'main' into develop commit 6c1667be23597513537f8190e2f55d65eb9b7c7a Author: vnugent <public@vaughnnugent.com> Date: Fri Mar 22 12:01:53 2024 -0400 refactor: Overhauled native library loading and lazy init commit ebf688f2f974295beabf7b5def7e6f6f150551d0 Author: vnugent <public@vaughnnugent.com> Date: Wed Mar 20 22:16:17 2024 -0400 refactor: Update compression header files and macros + Ci build commit 9c7b564911080ccd5cbbb9851a0757b05e1e9047 Author: vnugent <public@vaughnnugent.com> Date: Tue Mar 19 21:54:49 2024 -0400 refactor: JWK overhaul & add length getter to FileUpload commit 6d8c3444e09561e5957491b3cc1ae858e0abdd14 Author: vnugent <public@vaughnnugent.com> Date: Mon Mar 18 16:13:20 2024 -0400 feat: Add FNV1a software checksum and basic correction tests commit 00d182088cecefc08ca80b1faee9bed3f215f40b Author: vnugent <public@vaughnnugent.com> Date: Fri Mar 15 01:05:27 2024 -0400 chore: #6 Use utils filewatcher instead of built-in commit d513c10d9895c6693519ef1d459c6a5a76929436 Author: vnugent <public@vaughnnugent.com> Date: Sun Mar 10 21:58:14 2024 -0400 source tree project location updated
Diffstat (limited to 'apps/VNLib.WebServer/src/VirtualHosts')
-rw-r--r--apps/VNLib.WebServer/src/VirtualHosts/FileCache.cs134
-rw-r--r--apps/VNLib.WebServer/src/VirtualHosts/IVirtualHostConfigBuilder.cs35
-rw-r--r--apps/VNLib.WebServer/src/VirtualHosts/JsonWebConfigBuilder.cs282
-rw-r--r--apps/VNLib.WebServer/src/VirtualHosts/SpecialHeaders.cs71
-rw-r--r--apps/VNLib.WebServer/src/VirtualHosts/VirtualHostConfig.cs101
-rw-r--r--apps/VNLib.WebServer/src/VirtualHosts/VirtualHostHooks.cs231
6 files changed, 854 insertions, 0 deletions
diff --git a/apps/VNLib.WebServer/src/VirtualHosts/FileCache.cs b/apps/VNLib.WebServer/src/VirtualHosts/FileCache.cs
new file mode 100644
index 0000000..de4efd9
--- /dev/null
+++ b/apps/VNLib.WebServer/src/VirtualHosts/FileCache.cs
@@ -0,0 +1,134 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.WebServer
+* File: FileCache.cs
+*
+* FileCache.cs is part of VNLib.WebServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.WebServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.WebServer 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+
+using VNLib.Net.Http;
+using VNLib.Utils;
+using VNLib.Utils.IO;
+
+namespace VNLib.WebServer
+{
+ /// <summary>
+ /// File the server will keep in memory and return to user when a specified error code is requested
+ /// </summary>
+ internal sealed class FileCache : VnDisposeable, IFSChangeHandler
+ {
+ private readonly string _filePath;
+
+ public readonly HttpStatusCode Code;
+
+ private Lazy<byte[]> _templateData;
+
+
+ /// <summary>
+ /// Catch an http error code and return the selected file to user
+ /// </summary>
+ /// <param name="code">Http status code to catch</param>
+ /// <param name="filePath">Path to file contating data to return to use on status code</param>
+ private FileCache(HttpStatusCode code, string filePath)
+ {
+ Code = code;
+ _filePath = filePath;
+ _templateData = new(LoadTemplateData);
+ }
+
+ private byte[] LoadTemplateData()
+ {
+ //Get file data as binary
+ return File.ReadAllBytes(_filePath);
+ }
+
+ /// <summary>
+ /// Gets a <see cref="IMemoryResponseReader"/> wrapper that may read a copy of the
+ /// file representation
+ /// </summary>
+ /// <returns>The <see cref="IMemoryResponseReader"/> wrapper around the file data</returns>
+ public IMemoryResponseReader GetReader() => new MemReader(_templateData.Value);
+
+
+ void IFSChangeHandler.OnFileChanged(FileSystemEventArgs e)
+ {
+ //Update lazy loader for new file update
+ _templateData = new(LoadTemplateData);
+ }
+
+ protected override void Free()
+ {
+ //Unsubscribe from file watcher
+ FileWatcher.Unsubscribe(_filePath, this);
+ }
+
+ /// <summary>
+ /// Create a new file cache for a specific error code
+ /// and begins listening for changes to the file
+ /// </summary>
+ /// <param name="code">The status code to produce the file for</param>
+ /// <param name="filePath">The path to the file to read</param>
+ /// <returns>The new <see cref="FileCache"/> instance if the file exists and is readable, null otherwise</returns>
+ public static FileCache? Create(HttpStatusCode code, string filePath)
+ {
+ //If the file does not exist, return null
+ if(!FileOperations.FileExists(filePath))
+ {
+ return null;
+ }
+
+ FileCache ff = new(code, filePath);
+
+ //Subscribe to file changes
+ FileWatcher.Subscribe(filePath, ff);
+
+ return ff;
+ }
+
+ private sealed class MemReader : IMemoryResponseReader
+ {
+ private readonly byte[] _memory;
+
+ private int _written;
+
+ public int Remaining { get; private set; }
+
+ internal MemReader(byte[] data)
+ {
+ //Store ref as memory
+ _memory = data;
+ Remaining = data.Length;
+ }
+
+ public void Advance(int written)
+ {
+ _written += written;
+ Remaining -= written;
+ }
+
+ void IMemoryResponseReader.Close() { }
+
+ ReadOnlyMemory<byte> IMemoryResponseReader.GetMemory() => _memory.AsMemory(_written, Remaining);
+ }
+ }
+} \ No newline at end of file
diff --git a/apps/VNLib.WebServer/src/VirtualHosts/IVirtualHostConfigBuilder.cs b/apps/VNLib.WebServer/src/VirtualHosts/IVirtualHostConfigBuilder.cs
new file mode 100644
index 0000000..7e837e7
--- /dev/null
+++ b/apps/VNLib.WebServer/src/VirtualHosts/IVirtualHostConfigBuilder.cs
@@ -0,0 +1,35 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.WebServer
+* File: IVirtualHostConfigBuilder.cs
+*
+* IVirtualHostConfigBuilder.cs is part of VNLib.WebServer which is
+* part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.WebServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.WebServer 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
+*/
+
+namespace VNLib.WebServer
+{
+ internal interface IVirtualHostConfigBuilder
+ {
+ /// <summary>
+ /// Gets the base configuration for the virtualhost
+ /// </summary>
+ /// <returns></returns>
+ VirtualHostConfig GetBaseConfig();
+ }
+}
diff --git a/apps/VNLib.WebServer/src/VirtualHosts/JsonWebConfigBuilder.cs b/apps/VNLib.WebServer/src/VirtualHosts/JsonWebConfigBuilder.cs
new file mode 100644
index 0000000..7f3b488
--- /dev/null
+++ b/apps/VNLib.WebServer/src/VirtualHosts/JsonWebConfigBuilder.cs
@@ -0,0 +1,282 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.WebServer
+* File: JsonWebConfigBuilder.cs
+*
+* JsonWebConfigBuilder.cs is part of VNLib.WebServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.WebServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.WebServer 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Data;
+using System.Linq;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+using VNLib.Utils.Logging;
+using VNLib.Utils.Extensions;
+
+using VNLib.WebServer.Config;
+using VNLib.WebServer.Config.Model;
+
+namespace VNLib.WebServer
+{
+
+ internal sealed partial class JsonWebConfigBuilder(VirtualHostServerConfig VhConfig, ILogProvider logger)
+ : IVirtualHostConfigBuilder
+ {
+ //Use pre-compiled default regex
+ private static readonly Regex DefaultRootRegex = MyRegex();
+
+ ///<inheritdoc/>
+ public VirtualHostConfig GetBaseConfig()
+ {
+ //Declare the vh config
+ return new()
+ {
+ //File root is required
+ RootDir = new(VhConfig.DirPath!),
+ LogProvider = logger,
+ ExecutionTimeout = GetExecutionTimeout(VhConfig),
+ WhiteList = GetIpWhitelist(VhConfig),
+ DownStreamServers = GetDownStreamServers(VhConfig),
+ ExcludedExtensions = GetExlcudedExtensions(VhConfig),
+ DefaultFiles = GetDefaultFiles(VhConfig),
+ PathFilter = GetPathFilter(VhConfig),
+ CacheDefault = TimeSpan.FromSeconds(VhConfig.CacheDefaultTimeSeconds),
+ AdditionalHeaders = GetConfigHeaders(VhConfig),
+ SpecialHeaders = GetSpecialHeaders(VhConfig),
+ FailureFiles = GetFailureFiles(VhConfig),
+ FilePathCacheMaxAge = TimeSpan.MaxValue,
+ Hostnames = GetHostnames(VhConfig),
+ Transports = GetInterfaces(VhConfig),
+ BlackList = GetIpBlacklist(VhConfig),
+ };
+ }
+
+ private static string[] GetHostnames(VirtualHostServerConfig conf)
+ {
+ Validate.EnsureNotNull(conf.Hostnames, "Hostnames array was set to null, you must define at least one hostname");
+
+ foreach (string hostname in conf.Hostnames)
+ {
+ Validate.EnsureNotNull(hostname, "Hostname is null, all hostnames must be defined");
+ }
+
+ return conf.Hostnames;
+ }
+
+ private static TransportInterface[] GetInterfaces(VirtualHostServerConfig conf)
+ {
+ Validate.EnsureNotNull(conf.Interfaces, "Interfaces array was set to null, you must define at least one network interface");
+ Validate.Assert(conf.Interfaces.Length > 0, $"You must define at least one interface for host");
+
+ for(int i = 0; i < conf.Interfaces.Length; i++)
+ {
+ TransportInterface iFace = conf.Interfaces[i];
+
+ Validate.EnsureNotNull(iFace, $"Vrtual host interface [{i}] is undefined");
+
+ Validate.EnsureNotNull(iFace.Address, $"The interface IP address is required for interface [{i}]");
+ Validate.EnsureValidIp(iFace.Address, $"The interface IP address is invalid for interface [{i}]");
+ Validate.EnsureRange(iFace.Port, 1, 65535, "Interface port");
+ }
+
+ return conf.Interfaces;
+ }
+
+ private static Regex GetPathFilter(VirtualHostServerConfig conf)
+ {
+ //Allow site to define a regex filter pattern
+ return conf.PathFilter is not null ? new(conf.PathFilter!) : DefaultRootRegex;
+ }
+
+ private FrozenDictionary<HttpStatusCode, FileCache> GetFailureFiles(VirtualHostServerConfig conf)
+ {
+ //if a failure file array is specified, capure all files and
+ if (conf.ErrorFiles is null || conf.ErrorFiles.Length < 1)
+ {
+ return new Dictionary<HttpStatusCode, FileCache>().ToFrozenDictionary();
+ }
+
+ //Get the error files
+ IEnumerable<KeyValuePair<HttpStatusCode, string>> ffs = conf.ErrorFiles
+ .Select(static f => new KeyValuePair<HttpStatusCode, string>((HttpStatusCode)f.Code, f.Path!));
+
+ //Create the file cache dictionary
+ (HttpStatusCode, string, FileCache?)[] loadCache = ffs.Select(static kv =>
+ {
+ FileCache? cached = FileCache.Create(kv.Key, kv.Value);
+ return (kv.Key, kv.Value, cached);
+
+ }).ToArray();
+
+ //Only include files that exist and were loaded
+ int loadedFiles = loadCache
+ .Where(static loadCache => loadCache.Item3 != null)
+ .Count();
+
+ string[] notFoundFiles = loadCache
+ .Where(static loadCache => loadCache.Item3 == null)
+ .Select(static l => Path.GetFileName(l.Item2))
+ .ToArray();
+
+ if (notFoundFiles.Length > 0)
+ {
+ logger.Warn("Failed to load error files {files} for host {hosts}", notFoundFiles, conf.Hostnames);
+ }
+
+ //init frozen dictionary from valid cached files
+ return loadCache
+ .Where(static kv => kv.Item3 != null)
+ .ToDictionary(static kv => kv.Item1, static kv => kv.Item3!)
+ .ToFrozenDictionary();
+ }
+
+ private static FrozenSet<IPAddress> GetDownStreamServers(VirtualHostServerConfig conf)
+ {
+ //Find downstream servers
+ HashSet<IPAddress>? downstreamServers = null;
+
+ //See if element is set
+ if (conf.DownstreamServers is not null)
+ {
+ //hash addresses, make is distinct
+ downstreamServers = conf.DownstreamServers
+ .Where(static addr => !string.IsNullOrWhiteSpace(addr))
+ .Select(static addr => IPAddress.Parse(addr))
+ .Distinct()
+ .ToHashSet();
+ }
+
+ return (downstreamServers ?? []).ToFrozenSet();
+ }
+
+ private static TimeSpan GetExecutionTimeout(VirtualHostServerConfig conf)
+ {
+ //Get the execution timeout
+ return TimeSpan.FromMilliseconds(conf.MaxExecutionTimeMs);
+ }
+
+ private static FrozenSet<IPAddress>? GetIpWhitelist(VirtualHostServerConfig conf)
+ {
+ if(conf.Whitelist is null)
+ {
+ return null;
+ }
+
+ //See if whitelist is defined, if so, get a distinct list of addresses
+ return conf.Whitelist
+ .Where(static addr => !string.IsNullOrWhiteSpace(addr))
+ .Select(static addr => IPAddress.Parse(addr))
+ .Distinct()
+ .ToHashSet()
+ .ToFrozenSet();
+ }
+
+ private static FrozenSet<IPAddress>? GetIpBlacklist(VirtualHostServerConfig conf)
+ {
+ if (conf.Blacklist is null)
+ {
+ return null;
+ }
+
+ //See if whitelist is defined, if so, get a distinct list of addresses
+ return conf.Blacklist
+ .Where(static addr => !string.IsNullOrWhiteSpace(addr))
+ .Select(static addr => IPAddress.Parse(addr))
+ .Distinct()
+ .ToHashSet()
+ .ToFrozenSet();
+ }
+
+ private static FrozenSet<string> GetExlcudedExtensions(VirtualHostServerConfig conf)
+ {
+ //Get exlucded/denied extensions from config, ignore null strings
+ if (conf.DenyExtensions is not null)
+ {
+ return conf.DenyExtensions
+ .Where(static s => !string.IsNullOrWhiteSpace(s))
+ .Distinct()
+ .ToHashSet()
+ .ToFrozenSet(StringComparer.OrdinalIgnoreCase);
+ }
+ else
+ {
+ return new HashSet<string>().ToFrozenSet();
+ }
+ }
+
+ private static IReadOnlyCollection<string> GetDefaultFiles(VirtualHostServerConfig conf)
+ {
+ if(conf.DefaultFiles is null)
+ {
+ return Array.Empty<string>();
+ }
+
+ //Get blocked extensions for the root
+ return conf.DefaultFiles
+ .Where(static s => !string.IsNullOrWhiteSpace(s))
+ .Distinct()
+ .ToList();
+ }
+
+ private static KeyValuePair<string, string>[] GetConfigHeaders(VirtualHostServerConfig conf)
+ {
+ if (conf.Headers is null)
+ {
+ return [];
+ }
+
+ //Enumerate kv headers
+ return conf.Headers
+ //Ignore empty keys or values
+ .Where(static p => !string.IsNullOrWhiteSpace(p.Key) && string.IsNullOrWhiteSpace(p.Value))
+ //Exclude special headers
+ .Where(static p => !SpecialHeaders.SpecialHeader.Contains(p.Key, StringComparer.OrdinalIgnoreCase))
+ .Select(static p => new KeyValuePair<string, string>(p.Key!, p.Value))
+ .ToArray();
+ }
+
+ private static FrozenDictionary<string, string> GetSpecialHeaders(VirtualHostServerConfig conf)
+ {
+ //get the headers array property
+ if (conf.Headers is null)
+ {
+ return new Dictionary<string, string>().ToFrozenDictionary();
+ }
+
+ //Enumerate kv header
+ return conf.Headers
+ //Ignore empty keys or values
+ .Where(static p => !string.IsNullOrWhiteSpace(p.Key) && !string.IsNullOrWhiteSpace(p.Value))
+ //Only include special headers
+ .Where(static p => SpecialHeaders.SpecialHeader.Contains(p.Key, StringComparer.OrdinalIgnoreCase))
+ //Create the special dictionary
+ .ToDictionary(static k => k.Key, static k => k.Value, StringComparer.OrdinalIgnoreCase)
+ .ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
+ }
+
+
+ [GeneratedRegex(@"(\/\.\.)|(\\\.\.)|[\[\]^*<>|`~'\n\r\t\n]|(\s$)|^(\s)", RegexOptions.Compiled)]
+ private static partial Regex MyRegex();
+ }
+}
diff --git a/apps/VNLib.WebServer/src/VirtualHosts/SpecialHeaders.cs b/apps/VNLib.WebServer/src/VirtualHosts/SpecialHeaders.cs
new file mode 100644
index 0000000..a4a797d
--- /dev/null
+++ b/apps/VNLib.WebServer/src/VirtualHosts/SpecialHeaders.cs
@@ -0,0 +1,71 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.WebServer
+* File: SpecialHeaders.cs
+*
+* SpecialHeaders.cs is part of VNLib.WebServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.WebServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.WebServer 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System.Runtime.CompilerServices;
+
+using VNLib.Net.Http;
+
+namespace VNLib.WebServer
+{
+ /// <summary>
+ /// Contains constants for internal/special headers by their name
+ /// </summary>
+ internal static class SpecialHeaders
+ {
+ public const string ContentSecPolicy = "Content-Security-Policy";
+ public const string XssProtection = "X-XSS-Protection";
+ public const string XContentOption = "X-Content-Type-Options";
+ public const string Hsts = "Strict-Transport-Security";
+ public const string Server = "Server";
+
+ /// <summary>
+ /// An array of the special headers to quickly compare against
+ /// </summary>
+ public static string[] SpecialHeader =
+ {
+ ContentSecPolicy,
+ XssProtection,
+ XContentOption,
+ Hsts,
+ Server
+ };
+
+ /// <summary>
+ /// Appends the special header by the given name, if it is present
+ /// in the current configruation's special headers
+ /// </summary>
+ /// <param name="config"></param>
+ /// <param name="server">The connection to set the response headers on</param>
+ /// <param name="headerName">The name of the special header to get</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void TrySetSpecialHeader(this VirtualHostConfig config, IConnectionInfo server, string headerName)
+ {
+ //Try to get the special header value,
+ if(config.SpecialHeaders.TryGetValue(headerName, out string? headerValue))
+ {
+ server.Headers.Append(headerName, headerValue);
+ }
+ }
+ }
+}
diff --git a/apps/VNLib.WebServer/src/VirtualHosts/VirtualHostConfig.cs b/apps/VNLib.WebServer/src/VirtualHosts/VirtualHostConfig.cs
new file mode 100644
index 0000000..8af58aa
--- /dev/null
+++ b/apps/VNLib.WebServer/src/VirtualHosts/VirtualHostConfig.cs
@@ -0,0 +1,101 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.WebServer
+* File: VirtualHostConfig.cs
+*
+* VirtualHostConfig.cs is part of VNLib.WebServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.WebServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.WebServer 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+using VNLib.Plugins.Essentials;
+using VNLib.Plugins.Essentials.ServiceStack.Construction;
+
+using VNLib.WebServer.Config.Model;
+
+namespace VNLib.WebServer
+{
+ /// <summary>
+ /// Implementation of <see cref="IEpProcessingOptions"/>
+ /// with <see cref="VirtualHostHooks"/> extra processing options
+ /// </summary>
+ internal sealed class VirtualHostConfig : VirtualHostConfiguration, IEpProcessingOptions
+ {
+ public VirtualHostConfig()
+ {
+ //Update file attributes
+ AllowedAttributes = FileAttributes.Archive | FileAttributes.Compressed | FileAttributes.Normal | FileAttributes.ReadOnly;
+ DissallowedAttributes = FileAttributes.Device
+ | FileAttributes.Directory
+ | FileAttributes.Encrypted
+ | FileAttributes.Hidden
+ | FileAttributes.IntegrityStream
+ | FileAttributes.Offline
+ | FileAttributes.ReparsePoint
+ | FileAttributes.System;
+ }
+
+ /// <summary>
+ /// A regex filter instance to filter incoming filesystem paths
+ /// </summary>
+ public Regex? PathFilter { get; init; }
+
+ /// <summary>
+ /// The default response entity cache value
+ /// </summary>
+ public required TimeSpan CacheDefault { get; init; }
+
+ /// <summary>
+ /// A collection of in-memory files to send in response to processing error
+ /// codes.
+ /// </summary>
+ public FrozenDictionary<HttpStatusCode, FileCache> FailureFiles { get; init; } = new Dictionary<HttpStatusCode, FileCache>().ToFrozenDictionary();
+
+ /// <summary>
+ /// Allows config to specify contant additional headers
+ /// </summary>
+ public KeyValuePair<string, string>[] AdditionalHeaders { get; init; } = Array.Empty<KeyValuePair<string, string>>();
+
+ /// <summary>
+ /// Contains internal headers used for specific purposes, cherrypicked from the config headers
+ /// </summary>
+ public FrozenDictionary<string, string> SpecialHeaders { get; init; } = new Dictionary<string, string>().ToFrozenDictionary();
+
+ /// <summary>
+ /// The array of interfaces the host wishes to listen on
+ /// </summary>
+ internal required TransportInterface[] Transports { get; init; }
+
+ /// <summary>
+ /// An optional whitelist set of ipaddresses that are allowed to make connections to this site
+ /// </summary>
+ internal required FrozenSet<IPAddress>? WhiteList { get; init; }
+
+ /// <summary>
+ /// An optional blacklist set of ipaddresses that are not allowed to make connections to this site
+ /// </summary>
+ internal required FrozenSet<IPAddress>? BlackList { get; init; }
+
+ }
+}
diff --git a/apps/VNLib.WebServer/src/VirtualHosts/VirtualHostHooks.cs b/apps/VNLib.WebServer/src/VirtualHosts/VirtualHostHooks.cs
new file mode 100644
index 0000000..e61b0a0
--- /dev/null
+++ b/apps/VNLib.WebServer/src/VirtualHosts/VirtualHostHooks.cs
@@ -0,0 +1,231 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.WebServer
+* File: VirtualHostHooks.cs
+*
+* VirtualHostHooks.cs is part of VNLib.WebServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.WebServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.WebServer 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Net;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+
+using VNLib.Net.Http;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins.Essentials;
+using VNLib.Plugins.Essentials.Extensions;
+using VNLib.Plugins.Essentials.ServiceStack.Construction;
+
+namespace VNLib.WebServer
+{
+
+ internal sealed class VirtualHostHooks(VirtualHostConfig config) : IVirtualHostHooks
+ {
+ private const int FILE_PATH_BUILDER_BUFFER_SIZE = 4096;
+
+ private static readonly string CultreInfo = CultureInfo.InstalledUICulture.Name;
+
+ private readonly string DefaultCacheString = HttpHelpers.GetCacheString(CacheType.Public, (int)config.CacheDefault.TotalSeconds);
+
+ public bool ErrorHandler(HttpStatusCode errorCode, IHttpEvent ev)
+ {
+ //Make sure the connection accepts html
+ if (ev.Server.Accepts(ContentType.Html) && config.FailureFiles.TryGetValue(errorCode, out FileCache? ff))
+ {
+ ev.Server.SetNoCache();
+ ev.CloseResponse(errorCode, ContentType.Html, ff.GetReader());
+ return true;
+ }
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveOptimization)]
+ public string TranslateResourcePath(string requestPath)
+ {
+
+ /*
+ * This function must safely translate a request URL as "unsanitized"
+ * user input to a safe filesystem path for a desired resource.
+ *
+ * A user may supply a custom regex file as a first line of defense
+ * for illegal fs characters.
+ *
+ * It is safe to assume the path is a local path, (not absolute) so
+ * it should not contain an illegal FS scheme
+ */
+
+ requestPath = config.PathFilter?.Replace(requestPath, string.Empty) ?? requestPath;
+
+ using UnsafeMemoryHandle<char> charBuffer = MemoryUtil.UnsafeAlloc<char>(FILE_PATH_BUILDER_BUFFER_SIZE);
+
+ ForwardOnlyWriter<char> sb = new(charBuffer.Span);
+
+ //Start with the root filename
+ sb.Append(config.RootDir.FullName);
+
+ //Supply a "leading" dir separator character
+ if (requestPath[0] != '/')
+ {
+ sb.Append('/');
+ }
+
+ //Add the path (trimmed for whitespace)
+ sb.Append(requestPath);
+
+ //Attmept to filter traversals
+ sb.Replace("..", string.Empty);
+
+ //if were on windows, convert to windows directory separators
+ if (OperatingSystem.IsWindows())
+ {
+ sb.Replace("/", "\\");
+ }
+ else
+ {
+ sb.Replace("\\", "/");
+ }
+
+ /*
+ * DEFAULT: If no file extension is listed or, is not a / separator, then
+ * add a .html extension
+ */
+ if (!Path.EndsInDirectorySeparator(requestPath) && !Path.HasExtension(requestPath))
+ {
+ sb.AppendSmall(".html");
+ }
+
+ return sb.ToString();
+ }
+
+ public void PreProcessEntityAsync(HttpEntity entity, out FileProcessArgs args)
+ {
+ args = FileProcessArgs.Continue;
+ }
+
+ public void PostProcessFile(HttpEntity entity, ref FileProcessArgs chosenRoutine)
+ {
+ //Do not respond to virtual processors
+ if (chosenRoutine == FileProcessArgs.VirtualSkip)
+ {
+ return;
+ }
+
+ //Get-set the x-content options headers from the client config
+ config.TrySetSpecialHeader(entity.Server, SpecialHeaders.XContentOption);
+
+ //Get the re-written url or
+ ReadOnlySpan<char> ext;
+ switch (chosenRoutine.Routine)
+ {
+ case FpRoutine.Deny:
+ case FpRoutine.Error:
+ case FpRoutine.NotFound:
+ case FpRoutine.Redirect:
+ {
+ ReadOnlySpan<char> filePath = entity.Server.Path.AsSpan();
+
+ //disable cache
+ entity.Server.SetNoCache();
+
+ //If the file is an html file or does not include an extension (inferred html)
+ ext = Path.GetExtension(filePath);
+ }
+ break;
+ case FpRoutine.ServeOther:
+ case FpRoutine.ServeOtherFQ:
+ {
+ ReadOnlySpan<char> filePath = chosenRoutine.Alternate.AsSpan();
+
+ //Use the alternate file path for extension
+ ext = Path.GetExtension(filePath);
+
+ //Set default cache
+ ContentType ct = HttpHelpers.GetContentTypeFromFile(filePath);
+ SetCache(entity, ct);
+ }
+ break;
+ default:
+ {
+ ReadOnlySpan<char> filePath = entity.Server.Path.AsSpan();
+
+ //If the file is an html file or does not include an extension (inferred html)
+ ext = Path.GetExtension(filePath);
+ if (ext.IsEmpty)
+ {
+ //If no extension, use .html extension
+ SetCache(entity, ContentType.Html);
+ }
+ else
+ {
+ //Set default cache
+ ContentType ct = HttpHelpers.GetContentTypeFromFile(filePath);
+ SetCache(entity, ct);
+ }
+ }
+ break;
+ }
+
+ //if the file is an html file, we are setting the csp and xss special headers
+ if (ext.IsEmpty || ext.Equals(".html", StringComparison.OrdinalIgnoreCase))
+ {
+ //Get/set xss protection header
+ config.TrySetSpecialHeader(entity.Server, SpecialHeaders.XssProtection);
+ config.TrySetSpecialHeader(entity.Server, SpecialHeaders.ContentSecPolicy);
+ }
+
+ //Set language of the server's os if the user code did not set it
+ if (!entity.Server.Headers.HeaderSet(HttpResponseHeader.ContentLanguage))
+ {
+ entity.Server.Headers[HttpResponseHeader.ContentLanguage] = CultreInfo;
+ }
+ }
+
+ private void SetCache(HttpEntity entity, ContentType ct)
+ {
+ //If request issued no cache request, set nocache headers
+ if (!entity.Server.NoCache())
+ {
+ //Otherwise set caching based on the file extension type
+ switch (ct)
+ {
+ case ContentType.Css:
+ case ContentType.Jpeg:
+ case ContentType.Javascript:
+ case ContentType.Svg:
+ case ContentType.Img:
+ case ContentType.Png:
+ case ContentType.Apng:
+ case ContentType.Avi:
+ case ContentType.Avif:
+ case ContentType.Gif:
+ entity.Server.Headers[HttpResponseHeader.CacheControl] = DefaultCacheString;
+ return;
+ case ContentType.NonSupported:
+ return;
+ default:
+ break;
+ }
+ }
+ entity.Server.SetNoCache();
+ }
+ }
+} \ No newline at end of file