diff options
author | vnugent <public@vaughnnugent.com> | 2024-08-04 17:14:10 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-08-04 17:14:10 -0400 |
commit | 0419f315e5689e043f311203ab8e61f69f1ee1d6 (patch) | |
tree | f077b9d32d787bdef6e2cded203bc459a23dae7b /apps/VNLib.WebServer/src/VirtualHosts | |
parent | 7be5d6648e633ba46a270ca5784de6f4a5a4e0a9 (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')
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 |