< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Hosting.KestrunHostMapExtensions
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHostMapExtensions.cs
Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
52%
Covered lines: 284
Uncovered lines: 256
Coverable lines: 540
Total lines: 1440
Line coverage: 52.5%
Branch coverage
73%
Covered branches: 172
Total branches: 233
Branch coverage: 73.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 14:53:17 Line coverage: 55.1% (140/254) Branch coverage: 61.6% (66/107) Total lines: 716 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/01/2025 - 04:08:24 Line coverage: 54.6% (140/256) Branch coverage: 60.5% (66/109) Total lines: 722 Tag: Kestrun/Kestrun@d6f26a131219b7a7fcb4e129af3193ec2ec4892910/13/2025 - 16:52:37 Line coverage: 62.5% (281/449) Branch coverage: 77.7% (164/211) Total lines: 1260 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 21:27:26 Line coverage: 71.1% (286/402) Branch coverage: 81.3% (170/209) Total lines: 1149 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559410/15/2025 - 22:47:55 Line coverage: 71.1% (286/402) Branch coverage: 81.3% (170/209) Total lines: 1148 Tag: Kestrun/Kestrun@f97c41150c4de89829eca919cc8b9b7e7df3df8e11/14/2025 - 12:29:34 Line coverage: 71.1% (286/402) Branch coverage: 81.3% (170/209) Total lines: 1146 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/12/2025 - 17:27:19 Line coverage: 34.5% (185/535) Branch coverage: 49.7% (114/229) Total lines: 1429 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/14/2025 - 20:04:52 Line coverage: 34.4% (186/540) Branch coverage: 49.3% (115/233) Total lines: 1439 Tag: Kestrun/Kestrun@a05ac8de57c6207e227b92ba360e9d58869ac80a12/15/2025 - 02:23:46 Line coverage: 52.5% (284/540) Branch coverage: 73.8% (172/233) Total lines: 1439 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c833012/18/2025 - 21:41:58 Line coverage: 52.5% (284/540) Branch coverage: 73.8% (172/233) Total lines: 1440 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff 08/26/2025 - 14:53:17 Line coverage: 55.1% (140/254) Branch coverage: 61.6% (66/107) Total lines: 716 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/01/2025 - 04:08:24 Line coverage: 54.6% (140/256) Branch coverage: 60.5% (66/109) Total lines: 722 Tag: Kestrun/Kestrun@d6f26a131219b7a7fcb4e129af3193ec2ec4892910/13/2025 - 16:52:37 Line coverage: 62.5% (281/449) Branch coverage: 77.7% (164/211) Total lines: 1260 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 21:27:26 Line coverage: 71.1% (286/402) Branch coverage: 81.3% (170/209) Total lines: 1149 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559410/15/2025 - 22:47:55 Line coverage: 71.1% (286/402) Branch coverage: 81.3% (170/209) Total lines: 1148 Tag: Kestrun/Kestrun@f97c41150c4de89829eca919cc8b9b7e7df3df8e11/14/2025 - 12:29:34 Line coverage: 71.1% (286/402) Branch coverage: 81.3% (170/209) Total lines: 1146 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/12/2025 - 17:27:19 Line coverage: 34.5% (185/535) Branch coverage: 49.7% (114/229) Total lines: 1429 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd12/14/2025 - 20:04:52 Line coverage: 34.4% (186/540) Branch coverage: 49.3% (115/233) Total lines: 1439 Tag: Kestrun/Kestrun@a05ac8de57c6207e227b92ba360e9d58869ac80a12/15/2025 - 02:23:46 Line coverage: 52.5% (284/540) Branch coverage: 73.8% (172/233) Total lines: 1439 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c833012/18/2025 - 21:41:58 Line coverage: 52.5% (284/540) Branch coverage: 73.8% (172/233) Total lines: 1440 Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
TryParse(...)100%11100%
AddMapRoute(...)100%11100%
AddMapRoute(...)100%22100%
AddMapRoute(...)66.66%8662.96%
AddOpenApiMapRoute(...)100%210%
AddMapRoute(...)83.33%66100%
AddMapRoute(...)100%44100%
AddMapRoute(...)100%44100%
CreateMapRoute(...)75%5461.11%
ValidateRouteOptions(...)91.66%121293.75%
CompileScript(...)57.14%8770%
handler()50%5460%
CreateAndRegisterRoute(...)83.33%6690.9%
AddMapOptions(...)100%11100%
TryParseEndpointSpec(...)100%88100%
TryParseUrlSpec(...)81.25%161694.44%
TryParseBracketedIpv6Spec(...)66.66%6687.5%
TryParseHostPortSpec(...)100%66100%
IsValidPort(...)100%22100%
ToRequireHost(...)100%22100%
IsIPv6Address(...)100%22100%
ApplyRequiredHost(...)96.15%2626100%
ApplyKestrunConventions(...)100%11100%
AddMetadata(...)83.33%17633.33%
ApplyShortCircuit(...)25%10428.57%
ApplyAnonymous(...)50%2260%
DisableAntiforgery(...)50%3240%
DisableResponseCompression(...)100%22100%
ApplyRateLimiting(...)50%3240%
ApplyAuthSchemes(...)100%88100%
ApplyPolicies(...)100%88100%
ApplyCors(...)33.33%14640%
ApplyOpenApiMetadata(...)0%7280%
AddHtmlTemplateRoute(...)0%620%
AddHtmlTemplateRoute(...)88.88%221876.92%
AddSwaggerUiRoute(...)100%210%
AddRedocUiRoute(...)100%210%
AddScalarUiRoute(...)100%210%
AddRapiDocUiRoute(...)100%210%
AddElementsUiRoute(...)100%210%
AddOpenApiUiRoute(...)0%210140%
AddHtmlRouteFromEmbeddedResource(...)100%210%
MapExists(...)100%11100%
MapExists(...)100%11100%
GetMapRouteOptions(...)50%22100%
AddAntiforgeryTokenRoute(...)0%620%
IsUnsafeVerb(...)80%1010100%
IsUnsafeMethod(...)100%66100%
ShouldValidateCsrf(...)100%66100%
TryValidateAntiforgeryAsync()0%620%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHostMapExtensions.cs

#LineLine coverage
 1using System.Net;
 2using System.Text.RegularExpressions;
 3using Kestrun.Hosting.Options;
 4using Kestrun.Languages;
 5using Kestrun.Models;
 6using Kestrun.OpenApi;
 7using Kestrun.Runtime;
 8using Kestrun.Scripting;
 9using Kestrun.TBuilder;
 10using Kestrun.Utilities;
 11using Microsoft.AspNetCore.Antiforgery;
 12using Microsoft.AspNetCore.Authorization;
 13using Microsoft.Extensions.Options;
 14using Microsoft.OpenApi;
 15using Serilog.Events;
 16
 17namespace Kestrun.Hosting;
 18
 19/// <summary>
 20/// Provides extension methods for mapping routes and handlers to the KestrunHost.
 21/// </summary>
 22public static partial class KestrunHostMapExtensions
 23{
 24    /// <summary>
 25    /// Public utility facade for endpoint specification parsing. This provides a stable API surface
 26    /// over the internal helper logic used by host route constraint processing.
 27    /// </summary>
 28    public static class EndpointSpecParser
 29    {
 30        /// <summary>
 31        /// Parses an endpoint specification into host, port and optional HTTPS flag.
 32        /// </summary>
 33        /// <param name="spec">Specification string. See <see cref="TryParseEndpointSpec"/> for accepted formats.</param
 34        /// <param name="host">Resolved host when successful, otherwise empty string.</param>
 35        /// <param name="port">Resolved port when successful, otherwise 0.</param>
 36        /// <param name="https">True for https, false for http, null when unspecified (host:port form).</param>
 37        /// <returns><c>true</c> if parsing succeeds; otherwise <c>false</c>.</returns>
 38        public static bool TryParse(string spec, out string host, out int port, out bool? https)
 1639            => TryParseEndpointSpec(spec, out host, out port, out https);
 40    }
 41    /// <summary>
 42    /// Represents a delegate that handles a Kestrun request with the provided context.
 43    /// </summary>
 44    /// <param name="Context">The context for the Kestrun request.</param>
 45    /// <returns>A task representing the asynchronous operation.</returns>
 46    public delegate Task KestrunHandler(KestrunContext Context);
 47
 48    /// <summary>
 49    /// Adds a native route to the KestrunHost for the specified pattern and HTTP verb.
 50    /// </summary>
 51    /// <param name="host">The KestrunHost instance.</param>
 52    /// <param name="pattern">The route pattern.</param>
 53    /// <param name="httpVerb">The HTTP verb for the route.</param>
 54    /// <param name="handler">The handler to execute for the route.</param>
 55    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 56    /// <param name="map">The endpoint convention builder for further configuration.</param>
 57    /// <returns>The KestrunHost instance for chaining.</returns>
 58    public static KestrunHost AddMapRoute(this KestrunHost host, string pattern, HttpVerb httpVerb, KestrunHandler handl
 259    host.AddMapRoute(pattern: pattern, httpVerbs: [httpVerb], handler: handler, out map, requireSchemes: requireSchemes)
 60
 61    /// <summary>
 62    /// Adds a native route to the KestrunHost for the specified pattern and HTTP verbs.
 63    /// </summary>
 64    /// <param name="host">The KestrunHost instance.</param>
 65    /// <param name="pattern">The route pattern.</param>
 66    /// <param name="httpVerbs">The HTTP verbs for the route.</param>
 67    /// <param name="handler">The handler to execute for the route.</param>
 68    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 69    /// <param name="map">The endpoint convention builder for further configuration.</param>
 70    /// <returns>The KestrunHost instance for chaining.</returns>
 71    public static KestrunHost AddMapRoute(this KestrunHost host, string pattern, IEnumerable<HttpVerb> httpVerbs, Kestru
 72    out IEndpointConventionBuilder? map, List<string>? requireSchemes = null)
 73    {
 274        return host.AddMapRoute(new MapRouteOptions
 275        {
 276            Pattern = pattern,
 277            HttpVerbs = [.. httpVerbs],
 278            ScriptCode = new LanguageOptions
 279            {
 280                Language = ScriptLanguage.Native,
 281            },
 282            RequireSchemes = requireSchemes ?? [] // No authorization by default
 283        }, handler, out map);
 84    }
 85
 86    /// <summary>
 87    /// Adds a native route to the KestrunHost using the specified MapRouteOptions and handler.
 88    /// </summary>
 89    /// <param name="host">The KestrunHost instance.</param>
 90    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 91    /// <param name="handler">The handler to execute for the route.</param>
 92    /// <param name="map">The endpoint convention builder for further configuration.</param>
 93    /// <returns>The KestrunHost instance for chaining.</returns>
 94    public static KestrunHost AddMapRoute(this KestrunHost host, MapRouteOptions options, KestrunHandler handler, out IE
 95    {
 296        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 97        {
 198            host.Logger.Debug("AddMapRoute called with options={Options}", options);
 99        }
 100        // Ensure the WebApplication is initialized
 2101        if (host.App is null)
 102        {
 0103            throw new InvalidOperationException("WebApplication is not initialized. Call EnableConfiguration first.");
 104        }
 105
 106        // Validate options
 1107        if (string.IsNullOrWhiteSpace(options.Pattern))
 108        {
 0109            throw new ArgumentException("Pattern cannot be null or empty.", nameof(options.Pattern));
 110        }
 111
 2112        string[] methods = [.. options.HttpVerbs.Select(v => v.ToMethodString())];
 1113        map = host.App.MapMethods(options.Pattern, methods, async context =>
 1114         {
 1115             // 🔒 CSRF validation only for the current request when that verb is unsafe (unless disabled)
 0116             if (ShouldValidateCsrf(options, context))
 1117             {
 0118                 if (!await TryValidateAntiforgeryAsync(context))
 1119                 {
 0120                     return; // already responded 400
 1121                 }
 1122             }
 0123             var req = await KestrunRequest.NewRequest(context);
 0124             var res = new KestrunResponse(req);
 0125             KestrunContext kestrunContext = new(host, req, res, context);
 0126             await handler(kestrunContext);
 0127             await res.ApplyTo(context.Response);
 1128         });
 129
 1130        host.AddMapOptions(map, options);
 131
 1132        host.Logger.Information("Added native route: {Pattern} with methods: {Methods}", options.Pattern, string.Join(",
 133        // Add to the feature queue for later processing
 1134        host.FeatureQueue.Add(host => host.AddMapRoute(options));
 1135        return host;
 136    }
 137
 138    /// <summary>
 139    /// Adds a route to the KestrunHost that serves OpenAPI documents based on the provided options.
 140    /// </summary>
 141    /// <param name="host">The KestrunHost instance.</param>
 142    /// <param name="options">The OpenApiMapRouteOptions instance.</param>
 143    /// <returns>The KestrunHost instance for chaining.</returns>
 144    public static KestrunHost AddOpenApiMapRoute(this KestrunHost host, OpenApiMapRouteOptions options)
 145    {
 0146        ArgumentNullException.ThrowIfNull(options);
 0147        ArgumentNullException.ThrowIfNull(host);
 148
 149        // Validate options
 0150        return host.AddMapRoute(options.MapOptions, async context =>
 0151        {
 0152            // Extract parameters
 0153            var refresh = false;
 0154            var docId = options.DocId;
 0155            OpenApiSpecVersion specVersion;
 0156            // Try to get version and format from route values
 0157            var version = context.Request.RouteValues[options.VersionVarName]?.ToString() ?? options.DefaultVersion;
 0158            var format = context.Request.RouteValues[options.FormatVarName]?.ToString() ?? options.DefaultFormat;
 0159            if (context.Request.Query.TryGetValue(options.RefreshVarName, out var value))
 0160            {
 0161                _ = bool.TryParse(value, out refresh);
 0162            }
 0163            // Try to get version and format from route values
 0164            try
 0165            {
 0166                specVersion = OpenApiSpecVersionExtensions.ParseOpenApiSpecVersion(version);
 0167                if (format is not "json" and not "yaml")
 0168                {
 0169                    throw new InvalidOperationException($"Unsupported OpenAPI format requested: {format}");
 0170                }
 0171            }
 0172            catch
 0173            {
 0174                host.Logger.Warning("Invalid OpenAPI version or format requested: {Version}, {Format}", version, format)
 0175                context.Response.StatusCode = 404; // Not Found
 0176                return;
 0177            }
 0178            // Refresh the document if requested
 0179            if (refresh)
 0180            {
 0181                host.Logger.Information("Refreshing OpenAPI document cache as requested.");
 0182                var doc = host.OpenApiDocumentDescriptor[docId];
 0183                doc.GenerateDoc();
 0184            }
 0185            // Serve the document in the requested format
 0186            if (format == "json")
 0187            {
 0188                var json = host.OpenApiDocumentDescriptor[docId].ToJson(specVersion);
 0189                await context.Response.WriteTextResponseAsync(json, 200, "application/json");
 0190            }
 0191            else
 0192            {
 0193                var yml = host.OpenApiDocumentDescriptor[docId].ToYaml(specVersion);
 0194                await context.Response.WriteTextResponseAsync(yml, 200, "application/yaml");
 0195            }
 0196        }, out _);
 197    }
 198
 199    /// <summary>
 200    /// Adds a route to the KestrunHost that executes a script block for the specified HTTP verb and pattern.
 201    /// </summary>
 202    /// <param name="host">The KestrunHost instance.</param>
 203    /// <param name="pattern">The route pattern.</param>
 204    /// <param name="httpVerbs">The HTTP verb for the route.</param>
 205    /// <param name="scriptBlock">The script block to execute.</param>
 206    /// <param name="language">The scripting language to use (default is PowerShell).</param>
 207    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 208    /// <param name="arguments">Optional dictionary of arguments to pass to the script.</param>
 209    /// <returns>The KestrunHost instance for chaining.</returns>
 210    public static KestrunHost AddMapRoute(this KestrunHost host, string pattern, HttpVerb httpVerbs, string scriptBlock,
 211                                     List<string>? requireSchemes = null,
 212                                 Dictionary<string, object?>? arguments = null)
 213    {
 11214        arguments ??= [];
 11215        return host.AddMapRoute(new MapRouteOptions
 11216        {
 11217            Pattern = pattern,
 11218            HttpVerbs = [httpVerbs],
 11219            ScriptCode = new LanguageOptions
 11220            {
 11221                Code = scriptBlock,
 11222                Language = language,
 11223                Arguments = arguments ?? [] // No additional arguments by default
 11224            },
 11225            RequireSchemes = requireSchemes ?? [], // No authorization by default
 11226        });
 227    }
 228
 229    /// <summary>
 230    /// Adds a route to the KestrunHost that executes a script block for the specified HTTP verbs and pattern.
 231    /// </summary>
 232    /// <param name="host">The KestrunHost instance.</param>
 233    /// <param name="pattern">The route pattern.</param>
 234    /// <param name="httpVerbs">The HTTP verbs for the route.</param>
 235    /// <param name="scriptBlock">The script block to execute.</param>
 236    /// <param name="language">The scripting language to use (default is PowerShell).</param>
 237    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 238    /// <param name="arguments">Optional dictionary of arguments to pass to the script.</param>
 239    /// <returns>The KestrunHost instance for chaining.</returns>
 240    public static KestrunHost AddMapRoute(this KestrunHost host, string pattern,
 241                                IEnumerable<HttpVerb> httpVerbs,
 242                                string scriptBlock,
 243                                ScriptLanguage language = ScriptLanguage.PowerShell,
 244                                List<string>? requireSchemes = null,
 245                                 Dictionary<string, object?>? arguments = null)
 246    {
 1247        return host.AddMapRoute(new MapRouteOptions
 1248        {
 1249            Pattern = pattern,
 1250            HttpVerbs = [.. httpVerbs],
 1251            ScriptCode = new LanguageOptions
 1252            {
 1253                Code = scriptBlock,
 1254                Language = language,
 1255                Arguments = arguments ?? [] // No additional arguments by default
 1256            },
 1257            RequireSchemes = requireSchemes ?? [], // No authorization by default
 1258        });
 259    }
 260
 261    /// <summary>
 262    /// Adds a route to the KestrunHost using the specified MapRouteOptions.
 263    /// </summary>
 264    /// <param name="host">The KestrunHost instance.</param>
 265    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 266    /// <returns>The KestrunHost instance for chaining.</returns>
 267    public static KestrunHost AddMapRoute(this KestrunHost host, MapRouteOptions options)
 268    {
 31269        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 270        {
 22271            host.Logger.Debug("AddMapRoute called with pattern={Pattern}, language={Language}, method={Methods}", option
 272        }
 31273        if (host.IsConfigured)
 274        {
 30275            _ = CreateMapRoute(host, options);
 276        }
 277        else
 278        {
 1279            _ = host.Use(app =>
 1280            {
 1281                _ = CreateMapRoute(host, options);
 2282            });
 283        }
 25284        return host; // for chaining
 285    }
 286
 287    /// <summary>
 288    /// Adds a route to the KestrunHost using the specified MapRouteOptions.
 289    /// </summary>
 290    /// <param name="host">The KestrunHost instance.</param>
 291    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 292    /// <returns>The IEndpointConventionBuilder for the created route.</returns>
 293    private static IEndpointConventionBuilder CreateMapRoute(KestrunHost host, MapRouteOptions options)
 294    {
 31295        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 296        {
 22297            host.Logger.Debug("AddMapRoute called with pattern={Pattern}, language={Language}, method={Methods}", option
 298        }
 299
 300        try
 301        {
 302            // Validate options and get normalized route options
 31303            if (!ValidateRouteOptions(host, options, out var routeOptions))
 304            {
 0305                return null!; // Route already exists and should be skipped
 306            }
 307
 30308            var logger = host.Logger.ForContext("Route", routeOptions.Pattern);
 309
 310            // Compile the script once – return a RequestDelegate
 30311            var compiled = CompileScript(host, options.ScriptCode);
 312
 313            // Create and register the route
 30314            return CreateAndRegisterRoute(host, routeOptions, compiled);
 315        }
 0316        catch (CompilationErrorException ex)
 317        {
 318            // Log the detailed compilation errors
 0319            host.Logger.Error($"Failed to add route '{options.Pattern}' due to compilation errors:");
 0320            host.Logger.Error(ex.GetDetailedErrorMessage());
 321
 322            // Re-throw with additional context
 0323            throw new InvalidOperationException(
 0324                $"Failed to compile {options.ScriptCode.Language} script for route '{options.Pattern}'. {ex.GetErrors().
 0325                ex);
 326        }
 6327        catch (Exception ex)
 328        {
 6329            throw new InvalidOperationException(
 6330                $"Failed to add route '{options.Pattern}' with method '{string.Join(", ", options.HttpVerbs)}' using {op
 6331                ex);
 332        }
 25333    }
 334
 335    /// <summary>
 336    /// Validates the host and options for adding a map route.
 337    /// </summary>
 338    /// <param name="host">The KestrunHost instance.</param>
 339    /// <param name="options">The MapRouteOptions to validate.</param>
 340    /// <param name="routeOptions">The validated route options with defaults applied.</param>
 341    /// <returns>True if validation passes and route should be added; false if duplicate route should be skipped.</retur
 342    /// <exception cref="InvalidOperationException">Thrown when WebApplication is not initialized or route already exist
 343    /// <exception cref="ArgumentException">Thrown when required options are invalid.</exception>
 344    internal static bool ValidateRouteOptions(KestrunHost host, MapRouteOptions options, out MapRouteOptions routeOption
 345    {
 346        // Ensure the WebApplication is initialized
 40347        if (host.App is null)
 348        {
 0349            throw new InvalidOperationException("WebApplication is not initialized. Call EnableConfiguration first.");
 350        }
 351
 352        // Validate options
 39353        if (string.IsNullOrWhiteSpace(options.Pattern))
 354        {
 2355            throw new ArgumentException("Pattern cannot be null or empty.", nameof(options.Pattern));
 356        }
 357
 358        // Validate code
 37359        if (string.IsNullOrWhiteSpace(options.ScriptCode.Code))
 360        {
 2361            throw new ArgumentException("ScriptBlock cannot be null or empty.", nameof(options.ScriptCode.Code));
 362        }
 363
 35364        routeOptions = options;
 35365        if (options.HttpVerbs.Count == 0)
 366        {
 367            // If no HTTP verbs were specified, default to GET.
 2368            routeOptions.HttpVerbs = [HttpVerb.Get];
 369        }
 370
 35371        if (MapExists(host, routeOptions.Pattern, routeOptions.HttpVerbs))
 372        {
 3373            var msg = $"Route '{routeOptions.Pattern}' with method(s) {string.Join(", ", routeOptions.HttpVerbs)} alread
 3374            if (options.ThrowOnDuplicate)
 375            {
 2376                throw new InvalidOperationException(msg);
 377            }
 378
 1379            host.Logger.Warning(msg);
 1380            return false; // Skip this route
 381        }
 382
 32383        return true; // Continue with route creation
 384    }
 385
 386    /// <summary>
 387    /// Compiles the script code for the specified language.
 388    /// </summary>
 389    /// <param name="host">The KestrunHost instance.</param>
 390    /// <param name="options">The language options containing the script code and language.</param>
 391    /// <returns>A compiled RequestDelegate that can handle HTTP requests.</returns>
 392    /// <exception cref="NotSupportedException">Thrown when the script language is not supported.</exception>
 393    internal static RequestDelegate CompileScript(this KestrunHost host, LanguageOptions options)
 394    {
 37395        return options.Language switch
 37396        {
 1397            ScriptLanguage.PowerShell => PowerShellDelegateBuilder.Build(host, options.Code!, options.Arguments),
 33398            ScriptLanguage.CSharp => CSharpDelegateBuilder.Build(host, options.Code!, options.Arguments, options.ExtraIm
 2399            ScriptLanguage.VBNet => VBNetDelegateBuilder.Build(host, options.Code!, options.Arguments, options.ExtraImpo
 0400            ScriptLanguage.FSharp => FSharpDelegateBuilder.Build(host, options.Code!), // F# scripting not implemented
 0401            ScriptLanguage.Python => PyDelegateBuilder.Build(host, options.Code!),
 0402            ScriptLanguage.JavaScript => JScriptDelegateBuilder.Build(host, options.Code!),
 1403            _ => throw new NotSupportedException(options.Language.ToString())
 37404        };
 405    }
 406
 407    /// <summary>
 408    /// Creates and registers a route with the specified options and compiled handler.
 409    /// </summary>
 410    /// <param name="host">The KestrunHost instance.</param>
 411    /// <param name="routeOptions">The validated route options.</param>
 412    /// <param name="compiled">The compiled script delegate.</param>
 413    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 414    internal static IEndpointConventionBuilder CreateAndRegisterRoute(KestrunHost host, MapRouteOptions routeOptions, Re
 415    {
 416        // Wrap with CSRF validation
 417        async Task handler(HttpContext ctx)
 418        {
 11419            if (ShouldValidateCsrf(routeOptions, ctx))
 420            {
 0421                if (!await TryValidateAntiforgeryAsync(ctx))
 422                {
 0423                    return; // already responded 400
 424                }
 425            }
 11426            await compiled(ctx);
 6427        }
 428
 67429        string[] methods = [.. routeOptions.HttpVerbs.Select(v => v.ToMethodString())];
 32430        var map = host.App!.MapMethods(routeOptions.Pattern!, methods, handler).WithLanguage(routeOptions.ScriptCode.Lan
 431
 32432        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 433        {
 23434            host.Logger.Debug("Mapped route: {Pattern} with methods: {Methods}", routeOptions.Pattern, string.Join(", ",
 435        }
 436
 32437        host.AddMapOptions(map, routeOptions);
 438
 439        // Register OpenAPI metadata for each verb
 114440        foreach (var method in routeOptions.HttpVerbs)
 441        {
 30442            if (routeOptions.OpenAPI.TryGetValue(method, out var value))
 443            {
 0444                ApplyOpenApiMetadata(host, map, value);
 445            }
 446            // Register the route to prevent duplicates
 30447            host._registeredRoutes[(routeOptions.Pattern!, method)] = routeOptions;
 448        }
 449
 27450        host.Logger.Information("Added route: {Pattern} with methods: {Methods}", routeOptions.Pattern, string.Join(", "
 27451        return map;
 452    }
 453
 454    /// <summary>
 455    /// Adds additional mapping options to the route.
 456    /// </summary>
 457    /// <param name="host">The Kestrun host.</param>
 458    /// <param name="map">The endpoint convention builder.</param>
 459    /// <param name="options">The mapping options.</param>
 460    internal static void AddMapOptions(this KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 461    {
 35462        ApplyShortCircuit(host, map, options);
 35463        ApplyAnonymous(host, map, options);
 35464        DisableAntiforgery(host, map, options);
 35465        DisableResponseCompression(host, map, options);
 35466        ApplyRateLimiting(host, map, options);
 35467        ApplyAuthSchemes(host, map, options);
 34468        ApplyPolicies(host, map, options);
 33469        ApplyCors(host, map, options);
 33470        ApplyRequiredHost(host, map, options);
 30471        AddMetadata(host, map, options);
 30472    }
 473
 474    /// <summary>
 475    /// Tries to parse an endpoint specification string into its components: host, port, and HTTPS flag.
 476    /// </summary>
 477    /// <param name="spec">The endpoint specification string.</param>
 478    /// <param name="host">The host component.</param>
 479    /// <param name="port">The port component.</param>
 480    /// <param name="https">
 481    /// Indicates HTTPS (<c>true</c>) or HTTP (<c>false</c>) when the scheme is explicitly specified via a full URL.
 482    /// For host:port forms where no scheme information is available the value is <c>null</c>.
 483    /// </param>
 484    /// <returns>
 485    /// <c>true</c> if parsing succeeds; otherwise <c>false</c> and <paramref name="host"/> will be <c>string.Empty</c> 
 486    /// </returns>
 487    /// <remarks>
 488    /// Accepted formats (in priority order):
 489    /// <list type="bullet">
 490    /// <item><description>Full URL: <c>https://host:port</c>, <c>http://host:port</c>, IPv6 literal allowed in brackets
 491    /// <item><description>Bracketed IPv6 host &amp; port: <c>[::1]:5000</c>, <c>[2001:db8::1]:8080</c>.</description></
 492    /// <item><description>Host or IPv4 with port: <c>localhost:5000</c>, <c>127.0.0.1:8080</c>, <c>example.com:443</c>.
 493    /// </list>
 494    /// Unsupported / rejected examples: non http(s) schemes (e.g. <c>ftp://</c>), missing port in host:port form, empty
 495    /// </remarks>
 496    public static bool TryParseEndpointSpec(string spec, out string host, out int port, out bool? https)
 497    {
 171498        host = ""; port = 0; https = null;
 499
 57500        if (string.IsNullOrWhiteSpace(spec))
 501        {
 5502            return false;
 503        }
 504
 505        // 1. Try full URL form first
 52506        if (TryParseUrlSpec(spec, out host, out port, out https))
 507        {
 16508            return true;
 509        }
 510
 511        // 2. Bracketed IPv6 literal with port: [::1]:5000
 36512        if (TryParseBracketedIpv6Spec(spec, out host, out port))
 513        {
 4514            return true; // https stays null (not specified)
 515        }
 516
 517        // 3. Regular host:port (hostname, IPv4, or raw IPv6 w/out brackets not supported here)
 32518        if (TryParseHostPortSpec(spec, out host, out port))
 519        {
 12520            return true; // https stays null (not specified)
 521        }
 522
 523        // No match
 60524        host = ""; port = 0; https = null;
 20525        return false;
 526    }
 527
 528    /// <summary>
 529    /// Tries to parse a full URL endpoint specification.
 530    /// </summary>
 531    /// <param name="spec">The endpoint specification string.</param>
 532    /// <param name="host">The parsed host component.</param>
 533    /// <param name="port">The parsed port component.</param>
 534    /// <param name="https">The parsed HTTPS flag.</param>
 535    /// <returns><c>true</c> if parsing succeeded; otherwise <c>false</c>.</returns>
 536    private static bool TryParseUrlSpec(string spec, out string host, out int port, out bool? https)
 537    {
 156538        host = ""; port = 0; https = null;
 539        // Fast rejection for an explicitly empty port (e.g. "https://localhost:" or "http://[::1]:")
 540        // Uri.TryCreate will happily parse these and supply the default scheme port (80/443),
 541        // which would make us treat an intentionally empty port as a valid implicit port.
 542        // The accepted formats require either no colon at all (implicit default) OR a colon followed by digits.
 543        // Therefore pattern: scheme:// host-part : end-of-string (no digits after colon) should be rejected.
 52544        if (EmptyPortDetectionRegex().IsMatch(spec))
 545        {
 2546            return false;
 547        }
 50548        if (!Uri.TryCreate(spec, UriKind.Absolute, out var uri))
 549        {
 22550            return false;
 551        }
 28552        if (!(uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) ||
 28553              uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase)))
 554        {
 12555            return false; // Not http/https → let other parsers try
 556        }
 16557        if (uri.Authority.EndsWith(':'))
 558        {
 0559            return false; // reject empty port like https://localhost:
 560        }
 16561        host = uri.Host;
 16562        port = uri.Port;
 16563        https = uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)
 16564            ? true
 16565            : uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase)
 16566                ? false
 16567                : null;
 16568        return !string.IsNullOrWhiteSpace(host) && IsValidPort(port);
 569    }
 570
 571    /// <summary>
 572    /// Tries to parse a bracketed IPv6 endpoint specification.
 573    /// </summary>
 574    /// <param name="spec">The endpoint specification string.</param>
 575    /// <param name="host">The parsed host component.</param>
 576    /// <param name="port">The parsed port component.</param>
 577    /// <returns><c>true</c> if parsing succeeded; otherwise <c>false</c>.</returns>
 578    private static bool TryParseBracketedIpv6Spec(string spec, out string host, out int port)
 579    {
 72580        host = ""; port = 0;
 36581        var m = BracketedIpv6SpecMatcher().Match(spec);
 36582        if (!m.Success)
 583        {
 32584            return false;
 585        }
 4586        host = m.Groups[1].Value;
 4587        if (!int.TryParse(m.Groups[2].Value, out port) || !IsValidPort(port))
 588        {
 0589            host = ""; port = 0; return false;
 590        }
 4591        return !string.IsNullOrWhiteSpace(host);
 592    }
 593
 594    /// <summary>
 595    /// Tries to parse a host:port endpoint specification.
 596    /// </summary>
 597    /// <param name="spec">The endpoint specification string.</param>
 598    /// <param name="host">The parsed host component.</param>
 599    /// <param name="port">The parsed port component.</param>
 600    /// <returns><c>true</c> if parsing succeeded; otherwise <c>false</c>.</returns>
 601    private static bool TryParseHostPortSpec(string spec, out string host, out int port)
 602    {
 64603        host = ""; port = 0;
 32604        var m = HostPortSpecMatcher().Match(spec);
 32605        if (!m.Success)
 606        {
 17607            return false;
 608        }
 15609        host = m.Groups[1].Value;
 15610        if (!int.TryParse(m.Groups[2].Value, out port) || !IsValidPort(port))
 611        {
 9612            host = ""; port = 0; return false;
 613        }
 12614        return !string.IsNullOrWhiteSpace(host);
 615    }
 616    private const int MIN_PORT = 1;
 617    private const int MAX_PORT = 65535;
 618
 619    /// <summary>
 620    /// Validates that the port number is within the acceptable range (1-65535).
 621    /// </summary>
 622    /// <param name="port">The port number to validate.</param>
 623    /// <returns><c>true</c> if the port number is valid; otherwise, <c>false</c>.</returns>
 35624    private static bool IsValidPort(int port) => port is >= MIN_PORT and <= MAX_PORT;
 625
 626    /// <summary>
 627    /// Formats the host and port for use in RequireHost, adding brackets for IPv6 literals.
 628    /// </summary>
 629    /// <param name="host">The host component.</param>
 630    /// <param name="port">The port component.</param>
 631    /// <returns>The formatted host and port string.</returns>
 632    internal static string ToRequireHost(string host, int port) =>
 18633        IsIPv6Address(host) ? $"[{host}]:{port}" : $"{host}:{port}"; // IPv6 literals must be bracketed in RequireHost
 634
 635    /// <summary>
 636    /// Determines if the given host string is an IPv6 address.
 637    /// </summary>
 638    /// <param name="host">The host string to check.</param>
 639    /// <returns>True if the host is an IPv6 address; otherwise, false.</returns>
 18640    private static bool IsIPv6Address(string host) => IPAddress.TryParse(host, out var ip) && ip.AddressFamily == System
 641
 642    /// <summary>
 643    /// Applies required hosts to the route based on the specified endpoints in the options.
 644    /// </summary>
 645    /// <param name="host">The Kestrun host.</param>
 646    /// <param name="map">The endpoint convention builder.</param>
 647    /// <param name="options">The mapping options.</param>
 648    /// <exception cref="ArgumentException">Thrown when the specified endpoints are invalid.</exception>
 649    internal static void ApplyRequiredHost(this KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions option
 650    {
 33651        if (options.Endpoints is not { Length: > 0 })
 652        {
 25653            return;
 654        }
 655
 8656        var listeners = host.Options.Listeners;
 8657        var require = new List<string>();
 8658        var errs = new List<string>();
 659
 38660        foreach (var spec in options.Endpoints)
 661        {
 11662            if (!TryParseEndpointSpec(spec, out var eh, out var ep, out var eHttps))
 663            {
 2664                errs.Add($"'{spec}' must be 'host:port' or 'http(s)://host:port'.");
 2665                continue;
 666            }
 667
 668            // Is the host a numeric IP?
 9669            var isNumericHost = IPAddress.TryParse(eh, out var endpointIp);
 670
 671            // Find a compatible listener: same port, scheme (if specified), and IP match if numeric host.
 9672            var match = listeners.FirstOrDefault(l =>
 19673                l.Port == ep &&
 19674                (eHttps is null || l.UseHttps == eHttps.Value) &&
 19675                (!isNumericHost ||
 19676                 l.IPAddress.Equals(endpointIp) ||
 19677                 l.IPAddress.Equals(IPAddress.Any) ||
 19678                 l.IPAddress.Equals(IPAddress.IPv6Any)));
 679
 9680            if (match is null)
 681            {
 2682                errs.Add($"'{spec}' doesn't match any configured listener. " +
 4683                         $"Known: {string.Join(", ", listeners.Select(l => l.ToString()))}");
 2684                continue;
 685            }
 686
 7687            require.Add(ToRequireHost(eh, ep));
 688        }
 689
 8690        if (errs.Count > 0)
 691        {
 3692            throw new InvalidOperationException("Invalid Endpoints:" + Environment.NewLine + "  - " + string.Join(Enviro
 693        }
 5694        if (require.Count > 0)
 695        {
 5696            host.Logger.Verbose("Applying required hosts: {RequiredHosts} to route: {Pattern}",
 5697                string.Join(", ", require), options.Pattern);
 5698            _ = map.RequireHost([.. require]);
 699        }
 5700    }
 701
 702    /// <summary>
 703    /// Applies the same route conventions used by the AddMapRoute helpers to an arbitrary endpoint.
 704    /// </summary>
 705    /// <param name="host">The Kestrun host used for validation (auth schemes/policies).</param>
 706    /// <param name="builder">The endpoint convention builder to decorate.</param>
 707    /// <param name="configure">Delegate to configure a fresh <see cref="MapRouteOptions"/> instance. Only applicable pr
 708    /// <remarks>
 709    /// This is useful when you map endpoints manually via <c>app.MapGet</c>/<c>MapPost</c> and still want consistent be
 710    /// (auth, CORS, rate limiting, antiforgery disable, OpenAPI metadata, short-circuiting) without re-implementing log
 711    /// Validation notes:
 712    ///  - Pattern, Code are ignored if not relevant.
 713    ///  - Authentication schemes and policies are validated against the host registry.
 714    ///  - OpenAPI metadata is applied only when non-empty.
 715    /// </remarks>
 716    /// <returns>The original <paramref name="builder"/> for fluent chaining.</returns>
 717    public static IEndpointConventionBuilder ApplyKestrunConventions(this KestrunHost host, IEndpointConventionBuilder b
 718    {
 1719        ArgumentNullException.ThrowIfNull(host);
 1720        ArgumentNullException.ThrowIfNull(builder);
 1721        ArgumentNullException.ThrowIfNull(configure);
 722
 723        // Start with an empty options record (only convention-related fields will matter)
 1724        var options = new MapRouteOptions
 1725        {
 1726            Pattern = string.Empty,
 1727            HttpVerbs = [],
 1728            ScriptCode = new LanguageOptions
 1729            {
 1730                Language = ScriptLanguage.Native,
 1731                Code = string.Empty
 1732            }
 1733        };
 1734        configure(options);
 735
 736        // Reuse internal helper (kept internal to avoid accidental misuse) for actual application
 1737        host.AddMapOptions(builder, options);
 1738        return builder;
 739    }
 740    /// <summary>
 741    /// Adds metadata to the route from the script parameters.
 742    /// </summary>
 743    /// <param name="host">The Kestrun host.</param>
 744    /// <param name="map">The endpoint convention builder.</param>
 745    /// <param name="options">The mapping options.</param>
 746    private static void AddMetadata(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 747    {
 30748        if (options.ScriptCode is null || options.ScriptCode.Parameters is null || options.ScriptCode.Parameters.Count =
 749        {
 30750            return;
 751        }
 752
 0753        host.Logger.Verbose("Adding metadata to route: {Pattern}", options.Pattern);
 0754        _ = map.WithMetadata(options.ScriptCode.Parameters);
 0755        _ = map.WithMetadata(new DefaultResponseContentType(options.DefaultResponseContentType));
 0756    }
 757    /// <summary>
 758    /// Applies short-circuiting behavior to the route.
 759    /// </summary>
 760    /// <param name="host">The Kestrun host.</param>
 761    /// <param name="map">The endpoint convention builder.</param>
 762    /// <param name="options">The mapping options.</param>
 763    private static void ApplyShortCircuit(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 764    {
 35765        if (!options.ShortCircuit)
 766        {
 35767            return;
 768        }
 769
 0770        host.Logger.Verbose("Short-circuiting route: {Pattern} with status code: {StatusCode}", options.Pattern, options
 0771        if (options.ShortCircuitStatusCode is null)
 772        {
 0773            throw new ArgumentException("ShortCircuitStatusCode must be set if ShortCircuit is true.", nameof(options.Sh
 774        }
 775
 0776        _ = map.ShortCircuit(options.ShortCircuitStatusCode);
 0777    }
 778
 779    /// <summary>
 780    /// Applies anonymous access behavior to the route.
 781    /// </summary>
 782    /// <param name="host">The Kestrun host.</param>
 783    /// <param name="map">The endpoint convention builder.</param>
 784    /// <param name="options">The mapping options.</param>
 785    private static void ApplyAnonymous(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 786    {
 35787        if (options.AllowAnonymous)
 788        {
 0789            host.Logger.Verbose("Allowing anonymous access for route: {Pattern}", options.Pattern);
 0790            _ = map.AllowAnonymous();
 791        }
 792        else
 793        {
 35794            host.Logger.Debug("No anonymous access allowed for route: {Pattern}", options.Pattern);
 795        }
 35796    }
 797
 798    /// <summary>
 799    /// Disables anti-forgery behavior to the route.
 800    /// </summary>
 801    /// <param name="host">The Kestrun host.</param>
 802    /// <param name="map">The endpoint convention builder.</param>
 803    /// <param name="options">The mapping options.</param>
 804    private static void DisableAntiforgery(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 805    {
 35806        if (!options.DisableAntiforgery)
 807        {
 35808            return;
 809        }
 810
 0811        _ = map.DisableAntiforgery();
 0812        host.Logger.Verbose("CSRF protection disabled for route: {Pattern}", options.Pattern);
 0813    }
 814
 815    /// <summary>
 816    /// Disables response compression for the route.
 817    /// </summary>
 818    /// <param name="host">The Kestrun host.</param>
 819    /// <param name="map">The endpoint convention builder.</param>
 820    /// <param name="options">The mapping options.</param>
 821    private static void DisableResponseCompression(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions opt
 822    {
 35823        if (!options.DisableResponseCompression)
 824        {
 34825            return;
 826        }
 827
 1828        _ = map.DisableResponseCompression();
 1829        host.Logger.Verbose("Response compression disabled for route: {Pattern}", options.Pattern);
 1830    }
 831    /// <summary>
 832    /// Applies rate limiting behavior to the route.
 833    /// </summary>
 834    /// <param name="host">The Kestrun host.</param>
 835    /// <param name="map">The endpoint convention builder.</param>
 836    /// <param name="options">The mapping options.</param>
 837    private static void ApplyRateLimiting(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 838    {
 35839        if (string.IsNullOrWhiteSpace(options.RateLimitPolicyName))
 840        {
 35841            return;
 842        }
 843
 0844        host.Logger.Verbose("Applying rate limit policy: {RateLimitPolicyName} to route: {Pattern}", options.RateLimitPo
 0845        _ = map.RequireRateLimiting(options.RateLimitPolicyName);
 0846    }
 847
 848    /// <summary>
 849    /// Applies authentication schemes to the route.
 850    /// </summary>
 851    /// <param name="host">The Kestrun host.</param>
 852    /// <param name="map">The endpoint convention builder.</param>
 853    /// <param name="options">The mapping options.</param>
 854    private static void ApplyAuthSchemes(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 855    {
 35856        if (options.RequireSchemes is not null && options.RequireSchemes.Count != 0)
 857        {
 7858            foreach (var schema in options.RequireSchemes)
 859            {
 2860                if (!host.HasAuthScheme(schema))
 861                {
 1862                    throw new ArgumentException($"Authentication scheme '{schema}' is not registered.", nameof(options.R
 863                }
 864            }
 1865            host.Logger.Verbose("Requiring authorization for route: {Pattern} with policies: {Policies}", options.Patter
 1866            _ = map.RequireAuthorization(new AuthorizeAttribute
 1867            {
 1868                AuthenticationSchemes = string.Join(',', options.RequireSchemes)
 1869            });
 870        }
 871        else
 872        {
 33873            host.Logger.Debug("No authorization required for route: {Pattern}", options.Pattern);
 874        }
 33875    }
 876
 877    /// <summary>
 878    /// Applies authorization policies to the route.
 879    /// </summary>
 880    /// <param name="host">The Kestrun host.</param>
 881    /// <param name="map">The endpoint convention builder.</param>
 882    /// <param name="options">The mapping options.</param>
 883    private static void ApplyPolicies(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 884    {
 34885        if (options.RequirePolicies is not null && options.RequirePolicies.Count != 0)
 886        {
 7887            foreach (var policy in options.RequirePolicies)
 888            {
 2889                if (!host.HasAuthPolicy(policy))
 890                {
 1891                    throw new ArgumentException($"Authorization policy '{policy}' is not registered.", nameof(options.Re
 892                }
 893            }
 1894            _ = map.RequireAuthorization(options.RequirePolicies.ToArray());
 895        }
 896        else
 897        {
 32898            host.Logger.Debug("No authorization policies required for route: {Pattern}", options.Pattern);
 899        }
 32900    }
 901    /// <summary>
 902    /// Applies CORS behavior to the route.
 903    /// </summary>
 904    /// <param name="host">The Kestrun host.</param>
 905    /// <param name="map">The endpoint convention builder.</param>
 906    /// <param name="options">The mapping options.</param>
 907    private static void ApplyCors(KestrunHost host, IEndpointConventionBuilder map, MapRouteOptions options)
 908    {
 33909        if (!string.IsNullOrWhiteSpace(options.CorsPolicy))
 910        {
 0911            if (!host.DefinedCorsPolicyNames.Contains(options.CorsPolicy))
 912            {
 0913                throw new ArgumentException($"CORS policy '{options.CorsPolicy}' is not registered.");
 914            }
 0915            host.Logger.Verbose("Applying CORS policy: {CorsPolicy} to route: {Pattern}", options.CorsPolicy, options.Pa
 0916            _ = map.RequireCors(options.CorsPolicy);
 0917            return;
 918        }
 919        // No per-route policy requested.
 33920        if (host.CorsPolicyDefined)
 921        {
 0922            host.Logger.Verbose("No per-route CORS policy set for route: {Pattern}; default CORS policy will apply.", op
 923        }
 924        else
 925        {
 33926            host.Logger.Debug("No CORS policy configured for route: {Pattern}", options.Pattern);
 927        }
 33928    }
 929
 930    /// <summary>
 931    /// Applies OpenAPI metadata to the route.
 932    /// </summary>
 933    /// <param name="host">The Kestrun host.</param>
 934    /// <param name="map">The endpoint convention builder.</param>
 935    /// <param name="openAPI">The OpenAPI metadata.</param>
 936    private static void ApplyOpenApiMetadata(KestrunHost host, IEndpointConventionBuilder map, OpenAPIMetadata openAPI)
 937    {
 0938        if (!string.IsNullOrEmpty(openAPI.OperationId))
 939        {
 0940            host.Logger.Verbose("Adding OpenAPI metadata for route: {Pattern} with OperationId: {OperationId}", openAPI.
 0941            _ = map.WithName(openAPI.OperationId);
 942        }
 943
 0944        if (!string.IsNullOrWhiteSpace(openAPI.Summary))
 945        {
 0946            host.Logger.Verbose("Adding OpenAPI summary for route: {Pattern} with Summary: {Summary}", openAPI.Pattern, 
 0947            _ = map.WithSummary(openAPI.Summary);
 948        }
 949
 0950        if (!string.IsNullOrWhiteSpace(openAPI.Description))
 951        {
 0952            host.Logger.Verbose("Adding OpenAPI description for route: {Pattern} with Description: {Description}", openA
 0953            _ = map.WithDescription(openAPI.Description);
 954        }
 955
 0956        if (openAPI.Tags.Count > 0)
 957        {
 0958            host.Logger.Verbose("Adding OpenAPI tags for route: {Pattern} with Tags: {Tags}", openAPI.Pattern, string.Jo
 0959            _ = map.WithTags([.. openAPI.Tags]);
 960        }
 0961    }
 962
 963    /// <summary>
 964    /// Adds an HTML template route to the KestrunHost for the specified pattern and HTML file path.
 965    /// </summary>
 966    /// <param name="host">The KestrunHost instance.</param>
 967    /// <param name="pattern">The route pattern.</param>
 968    /// <param name="htmlFilePath">The path to the HTML template file.</param>
 969    /// <param name="requireSchemes">Optional array of authorization schemes required for the route.</param>
 970    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 971    public static IEndpointConventionBuilder AddHtmlTemplateRoute(this KestrunHost host, string pattern, string htmlFile
 972    {
 0973        return host.AddHtmlTemplateRoute(new MapRouteOptions
 0974        {
 0975            Pattern = pattern,
 0976            HttpVerbs = [HttpVerb.Get],
 0977            RequireSchemes = requireSchemes ?? [] // No authorization by default
 0978        }, htmlFilePath);
 979    }
 980
 981    /// <summary>
 982    /// Adds an HTML template route to the KestrunHost using the specified MapRouteOptions and HTML file path.
 983    /// </summary>
 984    /// <param name="host">The KestrunHost instance.</param>
 985    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 986    /// <param name="htmlFilePath">The path to the HTML template file.</param>
 987    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 988    public static IEndpointConventionBuilder AddHtmlTemplateRoute(this KestrunHost host, MapRouteOptions options, string
 989    {
 3990        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 991        {
 2992            host.Logger.Debug("Adding HTML template route: {Pattern}", options.Pattern);
 993        }
 994
 3995        if (options.HttpVerbs.Count != 0 &&
 3996            (options.HttpVerbs.Count > 1 || options.HttpVerbs.First() != HttpVerb.Get))
 997        {
 1998            host.Logger.Error("HTML template routes only support GET requests. Provided HTTP verbs: {HttpVerbs}", string
 1999            throw new ArgumentException("HTML template routes only support GET requests.", nameof(options.HttpVerbs));
 1000        }
 21001        if (string.IsNullOrWhiteSpace(htmlFilePath) || !File.Exists(htmlFilePath))
 1002        {
 11003            host.Logger.Error("HTML file path is null, empty, or does not exist: {HtmlFilePath}", htmlFilePath);
 11004            throw new FileNotFoundException("HTML file not found.", htmlFilePath);
 1005        }
 1006
 11007        if (string.IsNullOrWhiteSpace(options.Pattern))
 1008        {
 01009            host.Logger.Error("Pattern cannot be null or empty.");
 01010            throw new ArgumentException("Pattern cannot be null or empty.", nameof(options.Pattern));
 1011        }
 1012
 11013        _ = host.AddMapRoute(options.Pattern, HttpVerb.Get, async (ctx) =>
 11014          {
 11015              // ② Build your variables map
 01016              var vars = new Dictionary<string, object?>();
 01017              _ = VariablesMap.GetVariablesMap(ctx, ref vars);
 11018
 01019              await ctx.Response.WriteHtmlResponseFromFileAsync(htmlFilePath, vars, ctx.Response.StatusCode);
 11020          }, out var map);
 11021        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 1022        {
 11023            host.Logger.Debug("Mapped HTML template route: {Pattern} to file: {HtmlFilePath}", options.Pattern, htmlFile
 1024        }
 11025        if (map is null)
 1026        {
 01027            throw new InvalidOperationException("Failed to create HTML template route.");
 1028        }
 11029        AddMapOptions(host, map, options);
 11030        return map;
 1031    }
 1032
 1033    /// <summary>
 1034    /// Adds a Swagger UI route to the KestrunHost for the specified pattern and OpenAPI endpoint.
 1035    /// </summary>
 1036    /// <param name="host">The KestrunHost instance.</param>
 1037    /// <param name="options">The MapRouteOptions containing route configuration.</param>
 1038    /// <param name="openApiEndpoint">The URI of the OpenAPI endpoint.</param>
 1039    /// <returns>An IEndpointConventionBuilder for further configuration.</returns>
 1040    /// <exception cref="ArgumentException">Thrown when the provided options are invalid.</exception>
 1041    /// <exception cref="InvalidOperationException">Thrown when the Swagger UI route cannot be created.</exception>
 1042    public static IEndpointConventionBuilder AddSwaggerUiRoute(
 1043        this KestrunHost host,
 1044        MapRouteOptions options,
 1045        Uri openApiEndpoint)
 1046    {
 01047        return AddOpenApiUiRoute(
 01048            host,
 01049            options,
 01050            openApiEndpoint,
 01051            uiName: "Swagger",
 01052            defaultPattern: "/docs/swagger",
 01053            resourceName: "Kestrun.Assets.swagger-ui.html");
 1054    }
 1055
 1056    /// <summary>
 1057    /// Adds a Redoc UI route to the KestrunHost for the specified pattern and OpenAPI endpoint.
 1058    /// </summary>
 1059    /// <param name="host">The KestrunHost instance.</param>
 1060    /// <param name="options">The route mapping options.</param>
 1061    /// <param name="openApiEndpoint">The OpenAPI endpoint URI.</param>
 1062    /// <returns>An IEndpointConventionBuilder for the mapped route.</returns>
 1063    /// <exception cref="ArgumentException">Thrown when the provided options are invalid.</exception>
 1064    /// <exception cref="InvalidOperationException">Thrown when the Redoc UI route cannot be created.</exception>
 1065    public static IEndpointConventionBuilder AddRedocUiRoute(
 1066        this KestrunHost host,
 1067        MapRouteOptions options,
 1068        Uri openApiEndpoint)
 1069    {
 01070        return AddOpenApiUiRoute(
 01071            host,
 01072            options,
 01073            openApiEndpoint,
 01074            uiName: "Redoc",
 01075            defaultPattern: "/docs/redoc",
 01076            resourceName: "Kestrun.Assets.redoc-ui.html");
 1077    }
 1078
 1079    /// <summary>
 1080    /// Adds a Scalar UI route to the KestrunHost for the specified pattern and OpenAPI endpoint.
 1081    /// </summary>
 1082    /// <param name="host">The KestrunHost instance.</param>
 1083    /// <param name="options">The route mapping options.</param>
 1084    /// <param name="openApiEndpoint">The OpenAPI endpoint URI.</param>
 1085    /// <returns>An IEndpointConventionBuilder for the mapped route.</returns>
 1086    /// <exception cref="ArgumentException">Thrown when the provided options are invalid.</exception>
 1087    /// <exception cref="InvalidOperationException">Thrown when the Scalar UI route cannot be created.</exception>
 1088    public static IEndpointConventionBuilder AddScalarUiRoute(
 1089        this KestrunHost host,
 1090        MapRouteOptions options,
 1091        Uri openApiEndpoint)
 1092    {
 01093        return AddOpenApiUiRoute(
 01094            host,
 01095            options,
 01096            openApiEndpoint,
 01097            uiName: "Scalar",
 01098            defaultPattern: "/docs/scalar",
 01099            resourceName: "Kestrun.Assets.scalar.html");
 1100    }
 1101
 1102    /// <summary>
 1103    /// Adds a RapiDoc UI route to the KestrunHost for the specified pattern and OpenAPI endpoint.
 1104    /// </summary>
 1105    /// <param name="host">The KestrunHost instance.</param>
 1106    /// <param name="options">The route mapping options.</param>
 1107    /// <param name="openApiEndpoint">The OpenAPI endpoint URI.</param>
 1108    /// <returns>An IEndpointConventionBuilder for the mapped route.</returns>
 1109    public static IEndpointConventionBuilder AddRapiDocUiRoute(
 1110       this KestrunHost host,
 1111       MapRouteOptions options,
 1112       Uri openApiEndpoint)
 1113    {
 01114        return AddOpenApiUiRoute(
 01115            host,
 01116            options,
 01117            openApiEndpoint,
 01118            uiName: "RapiDoc",
 01119            defaultPattern: "/docs/rapidoc",
 01120            resourceName: "Kestrun.Assets.rapidoc.html");
 1121    }
 1122
 1123    /// <summary>
 1124    /// Adds an Elements UI route to the KestrunHost for the specified pattern and OpenAPI endpoint.
 1125    /// </summary>
 1126    /// <param name="host">The KestrunHost instance.</param>
 1127    /// <param name="options">The route mapping options.</param>
 1128    /// <param name="openApiEndpoint">The OpenAPI endpoint URI.</param>
 1129    /// <returns>An IEndpointConventionBuilder for the mapped route.</returns>
 1130    public static IEndpointConventionBuilder AddElementsUiRoute(
 1131      this KestrunHost host,
 1132      MapRouteOptions options,
 1133      Uri openApiEndpoint)
 1134    {
 01135        return AddOpenApiUiRoute(
 01136            host,
 01137            options,
 01138            openApiEndpoint,
 01139            uiName: "Elements",
 01140            defaultPattern: "/docs/elements",
 01141            resourceName: "Kestrun.Assets.elements.html");
 1142    }
 1143
 1144    /// <summary>
 1145    /// Adds an OpenAPI UI route to the KestrunHost for the specified pattern and OpenAPI endpoint.
 1146    /// </summary>
 1147    /// <param name="host">The KestrunHost instance.</param>
 1148    /// <param name="options">The route mapping options.</param>
 1149    /// <param name="openApiEndpoint">The OpenAPI endpoint URI.</param>
 1150    /// <param name="uiName">The name of the UI.</param>
 1151    /// <param name="defaultPattern">The default route pattern.</param>
 1152    /// <param name="resourceName">The embedded resource name.</param>
 1153    /// <returns>The endpoint convention builder for the mapped route.</returns>
 1154    /// <exception cref="ArgumentException">Thrown when the provided options are invalid.</exception>
 1155    /// <exception cref="InvalidOperationException">Thrown when the OpenAPI UI route cannot be created.</exception>
 1156    private static IEndpointConventionBuilder AddOpenApiUiRoute(
 1157        KestrunHost host,
 1158        MapRouteOptions options,
 1159        Uri openApiEndpoint,
 1160        string uiName,
 1161        string defaultPattern,
 1162        string resourceName)
 1163    {
 01164        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 1165        {
 01166            host.Logger.Debug(
 01167                "Adding {UiName} UI route: {Pattern} for OpenAPI endpoint: {OpenApiEndpoint}",
 01168                uiName,
 01169                options.Pattern,
 01170                openApiEndpoint);
 1171        }
 1172
 01173        if (options.HttpVerbs.Count != 0 &&
 01174            (options.HttpVerbs.Count > 1 || options.HttpVerbs.First() != HttpVerb.Get))
 1175        {
 01176            host.Logger.Error(
 01177                "{UiName} UI routes only support GET requests. Provided HTTP verbs: {HttpVerbs}",
 01178                uiName,
 01179                string.Join(", ", options.HttpVerbs));
 1180
 01181            throw new ArgumentException(
 01182                $"{uiName} UI routes only support GET requests.",
 01183                nameof(options.HttpVerbs));
 1184        }
 1185
 1186        // Set default pattern if not provided
 01187        if (string.IsNullOrWhiteSpace(options.Pattern))
 1188        {
 01189            options.Pattern = defaultPattern;
 1190        }
 1191
 1192        // Load embedded UI HTML
 01193        var map = AddHtmlRouteFromEmbeddedResource(host, options.Pattern, openApiEndpoint, resourceName);
 1194
 01195        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 1196        {
 01197            host.Logger.Debug(
 01198                "Mapped {UiName} UI route: {Pattern} for OpenAPI endpoint: {OpenApiEndpoint}",
 01199                uiName,
 01200                options.Pattern,
 01201                openApiEndpoint);
 1202        }
 1203
 01204        if (map is null)
 1205        {
 01206            throw new InvalidOperationException($"Failed to create {uiName} UI route.");
 1207        }
 1208
 01209        AddMapOptions(host, map, options);
 01210        return map;
 1211    }
 1212
 1213    /// <summary>
 1214    /// Add a HTML route from an embedded resource.
 1215    /// </summary>
 1216    /// <param name="host">The KestrunHost instance.</param>
 1217    /// <param name="pattern">The route pattern.</param>
 1218    /// <param name="openApiEndpoint">The OpenAPI endpoint URI.</param>
 1219    /// <param name="embeddedResource">The embedded resource name.</param>
 1220    /// <exception cref="InvalidOperationException"></exception>
 1221    private static IEndpointConventionBuilder? AddHtmlRouteFromEmbeddedResource(KestrunHost host, string pattern, Uri op
 1222    {
 01223        _ = host.AddMapRoute(pattern: pattern, httpVerb: HttpVerb.Get, async (ctx) =>
 01224          {
 01225              var asm = typeof(KestrunHostMapExtensions).Assembly;
 01226              using var stream = asm.GetManifestResourceStream(embeddedResource)
 01227                    ?? throw new InvalidOperationException($"Embedded HTML resource not found: {embeddedResource}");
 01228
 01229              using var ms = new MemoryStream();
 01230              stream.CopyTo(ms);
 01231              var htmlBuffer = ms.ToArray();
 01232              ctx.Response.ContentType = "text/html; charset=utf-8";
 01233              await ctx.Response.WriteHtmlResponseAsync(htmlBuffer, new Dictionary<string, object?>
 01234              {
 01235                  { "OPENAPI_ENDPOINT", openApiEndpoint.ToString() }
 01236              }, ctx.Response.StatusCode);
 01237          }, out var map);
 01238        return map;
 1239    }
 1240
 1241    /// <summary>
 1242    /// Checks if a route with the specified pattern and optional HTTP method exists in the KestrunHost.
 1243    /// </summary>
 1244    /// <param name="host">The KestrunHost instance.</param>
 1245    /// <param name="pattern">The route pattern to check.</param>
 1246    /// <param name="verbs">The optional HTTP method to check for the route.</param>
 1247    /// <returns>True if the route exists; otherwise, false.</returns>
 1248    public static bool MapExists(this KestrunHost host, string pattern, IEnumerable<HttpVerb> verbs)
 1249    {
 801250        var methodSet = verbs.Select(v => v.ToMethodString()).ToHashSet(StringComparer.OrdinalIgnoreCase);
 391251        return host._registeredRoutes.Keys
 111252            .Where(k => string.Equals(k.Pattern, pattern, StringComparison.OrdinalIgnoreCase))
 481253            .Any(k => methodSet.Contains(k.Method.ToMethodString()));
 1254    }
 1255
 1256    /// <summary>
 1257    /// Checks if a route with the specified pattern and optional HTTP method exists in the KestrunHost.
 1258    /// </summary>
 1259    /// <param name="host">The KestrunHost instance.</param>
 1260    /// <param name="pattern">The route pattern to check.</param>
 1261    /// <param name="verb">The optional HTTP method to check for the route.</param>
 1262    /// <returns>True if the route exists; otherwise, false.</returns>
 1263    public static bool MapExists(this KestrunHost host, string pattern, HttpVerb verb) =>
 91264        host._registeredRoutes.ContainsKey((pattern, verb));
 1265
 1266    /// <summary>
 1267    /// Retrieves the <see cref="MapRouteOptions"/> associated with a given route pattern and HTTP verb, if registered.
 1268    /// </summary>
 1269    /// <param name="host">The <see cref="KestrunHost"/> instance to search for registered routes.</param>
 1270    /// <param name="pattern">The route pattern to look up (e.g. <c>"/hello"</c>).</param>
 1271    /// <param name="verb">The HTTP verb to match (e.g. <see cref="HttpVerb.Get"/>).</param>
 1272    /// <returns>
 1273    /// The <see cref="MapRouteOptions"/> instance for the specified route if found; otherwise, <c>null</c>.
 1274    /// </returns>
 1275    /// <remarks>
 1276    /// This method checks the internal route registry and returns the route options if the pattern and verb
 1277    /// combination was previously added via <c>AddMapRoute</c>.
 1278    /// This lookup is case-insensitive for both the pattern and method.
 1279    /// </remarks>
 1280    /// <example>
 1281    /// <code>
 1282    /// var options = host.GetMapRouteOptions("/hello", HttpVerb.Get);
 1283    /// if (options != null)
 1284    /// {
 1285    ///     Console.WriteLine($"Route language: {options.Language}");
 1286    /// }
 1287    /// </code>
 1288    /// </example>
 1289    public static MapRouteOptions? GetMapRouteOptions(this KestrunHost host, string pattern, HttpVerb verb)
 1290    {
 41291        return host._registeredRoutes.TryGetValue((pattern, verb), out var options)
 41292            ? options
 41293            : null;
 1294    }
 1295
 1296    /// <summary>
 1297    /// Adds a GET endpoint that issues the antiforgery cookie and returns a JSON payload:
 1298    /// { token: "...", headerName: "X-CSRF-TOKEN" }.
 1299    /// The endpoint itself is exempt from antiforgery validation.
 1300    /// </summary>
 1301    /// <param name="host">The KestrunHost instance.</param>
 1302    /// <param name="pattern">The route path to expose (default "/csrf-token").</param>
 1303    /// <returns>IEndpointConventionBuilder for further configuration.</returns>
 1304    public static IEndpointConventionBuilder AddAntiforgeryTokenRoute(
 1305    this KestrunHost host,
 1306    string pattern = "/csrf-token")
 1307    {
 01308        ArgumentException.ThrowIfNullOrWhiteSpace(pattern);
 01309        if (host.App is null)
 1310        {
 01311            throw new InvalidOperationException("WebApplication is not initialized. Call EnableConfiguration first.");
 1312        }
 01313        var options = new MapRouteOptions
 01314        {
 01315            Pattern = pattern,
 01316            HttpVerbs = [HttpVerb.Get],
 01317            ScriptCode = new LanguageOptions
 01318            {
 01319                Language = ScriptLanguage.Native
 01320            },
 01321            DisableAntiforgery = true,
 01322            AllowAnonymous = true,
 01323        };
 1324
 1325        // OpenAPI = new() { Summary = "Get CSRF token", Description = "Returns antiforgery request token and header nam
 1326
 1327        // Map directly and write directly (no KestrunResponse.ApplyTo)
 01328        var map = host.App.MapMethods(options.Pattern, [HttpMethods.Get], async context =>
 01329        {
 01330            var af = context.RequestServices.GetRequiredService<IAntiforgery>();
 01331            var opts = context.RequestServices.GetRequiredService<IOptions<AntiforgeryOptions>>();
 01332
 01333            var tokens = af.GetAndStoreTokens(context);
 01334
 01335            // Strongly discourage caches (proxies/browsers) from storing this payload
 01336            context.Response.Headers.CacheControl = "no-store, no-cache, must-revalidate";
 01337            context.Response.Headers.Pragma = "no-cache";
 01338            context.Response.Headers.Expires = "0";
 01339
 01340            context.Response.ContentType = "application/json";
 01341            await context.Response.WriteAsJsonAsync(new
 01342            {
 01343                token = tokens.RequestToken,
 01344                headerName = opts.Value.HeaderName // may be null if not configured
 01345            });
 01346        });
 1347
 1348        // Apply your pipeline metadata (this adds DisableAntiforgery, CORS, rate limiting, OpenAPI, etc.)
 01349        host.AddMapOptions(map, options);
 1350
 1351        // (Optional) track in your registry for consistency / duplicate checks
 01352        host._registeredRoutes[(options.Pattern, HttpVerb.Get)] = options;
 1353
 01354        host.Logger.Information("Added token endpoint: {Pattern} (GET)", options.Pattern);
 01355        return map;
 1356    }
 1357
 1358    private static bool IsUnsafeVerb(HttpVerb v)
 41359        => v is HttpVerb.Post or HttpVerb.Put or HttpVerb.Patch or HttpVerb.Delete;
 1360
 1361    private static bool IsUnsafeMethod(string method)
 191362        => HttpMethods.IsPost(method) || HttpMethods.IsPut(method) || HttpMethods.IsPatch(method) || HttpMethods.IsDelet
 1363
 1364    // New precise helper: only validate for the actual incoming request method when that method is unsafe and antiforge
 1365    private static bool ShouldValidateCsrf(MapRouteOptions o, HttpContext ctx)
 1366    {
 201367        if (o.DisableAntiforgery)
 1368        {
 11369            return false;
 1370        }
 191371        if (!IsUnsafeMethod(ctx.Request.Method))
 1372        {
 141373            return false; // Safe verb (GET/HEAD/OPTIONS) -> skip
 1374        }
 1375        // Ensure the route was actually configured for this unsafe verb (defensive; normally true inside mapped delegat
 171376        return o.HttpVerbs.Any(v => string.Equals(v.ToMethodString(), ctx.Request.Method, StringComparison.OrdinalIgnore
 1377    }
 1378
 1379    private static async Task<bool> TryValidateAntiforgeryAsync(HttpContext ctx)
 1380    {
 01381        var af = ctx.RequestServices.GetService<IAntiforgery>();
 01382        if (af is null)
 1383        {
 01384            return true; // antiforgery not configured → do nothing
 1385        }
 1386
 1387        try
 1388        {
 01389            await af.ValidateRequestAsync(ctx);
 01390            return true;
 1391        }
 01392        catch (AntiforgeryValidationException ex)
 1393        {
 1394            // short-circuit with RFC 9110 problem+json
 01395            ctx.Response.StatusCode = StatusCodes.Status400BadRequest;
 01396            ctx.Response.ContentType = "application/problem+json";
 01397            await ctx.Response.WriteAsJsonAsync(new
 01398            {
 01399                type = "https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.1",
 01400                title = "Antiforgery validation failed",
 01401                status = 400,
 01402                detail = ex.Message
 01403            });
 01404            return false;
 1405        }
 01406    }
 1407
 1408    /// <summary>
 1409    /// Matches a bracketed IPv6 host:port specification in the format "[ipv6]:port", where:
 1410    /// - ipv6 is a valid IPv6 address (e.g. "::1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
 1411    /// - port is a numeric value between 1 and 65535
 1412    /// Examples of valid inputs:
 1413    ///   "[::1]:80"
 1414    ///   "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:443"
 1415    /// </summary>
 1416    [GeneratedRegex(@"^\[([^\]]+)\]:(\d+)$")]
 1417    private static partial Regex BracketedIpv6SpecMatcher();
 1418
 1419    /// <summary>
 1420    /// Matches a host:port specification in the format "host:port", where:
 1421    /// - host can be any string excluding ':' (to avoid confusion with IPv6 addresses)
 1422    /// - port is a numeric value between 1 and 65535
 1423    /// Examples of valid inputs:
 1424    ///   "example.com:80"
 1425    ///   "localhost:443"
 1426    ///   "[::1]:8080"  (IPv6 address in brackets)
 1427    /// </summary>
 1428    [GeneratedRegex(@"^([^:]+):(\d+)$")]
 1429    private static partial Regex HostPortSpecMatcher();
 1430
 1431    /// <summary>
 1432    /// Matches a URL that starts with "http://" or "https://", followed by a host (excluding '/', '?', or '#'), and end
 1433    /// Examples of valid inputs:
 1434    ///   "http://example.com:"
 1435    ///   "https://localhost:"
 1436    ///   "https://my-server:8080:"
 1437    /// </summary>
 1438    [GeneratedRegex(@"^https?://[^/\?#]+:$", RegexOptions.IgnoreCase, "en-US")]
 1439    private static partial Regex EmptyPortDetectionRegex();
 1440}

Methods/Properties

TryParse(System.String,System.String&,System.Int32&,System.Nullable`1<System.Boolean>&)
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder&,System.Collections.Generic.List`1<System.String>)
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<Kestrun.Utilities.HttpVerb>,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder&,System.Collections.Generic.List`1<System.String>)
AddMapRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.KestrunHostMapExtensions/KestrunHandler,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder&)
AddOpenApiMapRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.OpenApiMapRouteOptions)
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb,System.String,Kestrun.Scripting.ScriptLanguage,System.Collections.Generic.List`1<System.String>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddMapRoute(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<Kestrun.Utilities.HttpVerb>,System.String,Kestrun.Scripting.ScriptLanguage,System.Collections.Generic.List`1<System.String>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddMapRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions)
CreateMapRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions)
ValidateRouteOptions(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,Kestrun.Hosting.Options.MapRouteOptions&)
CompileScript(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.LanguageOptions)
handler()
CreateAndRegisterRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,Microsoft.AspNetCore.Http.RequestDelegate)
AddMapOptions(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
TryParseEndpointSpec(System.String,System.String&,System.Int32&,System.Nullable`1<System.Boolean>&)
TryParseUrlSpec(System.String,System.String&,System.Int32&,System.Nullable`1<System.Boolean>&)
TryParseBracketedIpv6Spec(System.String,System.String&,System.Int32&)
TryParseHostPortSpec(System.String,System.String&,System.Int32&)
IsValidPort(System.Int32)
ToRequireHost(System.String,System.Int32)
IsIPv6Address(System.String)
ApplyRequiredHost(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyKestrunConventions(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,System.Action`1<Kestrun.Hosting.Options.MapRouteOptions>)
AddMetadata(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyShortCircuit(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyAnonymous(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
DisableAntiforgery(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
DisableResponseCompression(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyRateLimiting(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyAuthSchemes(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyPolicies(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyCors(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.MapRouteOptions)
ApplyOpenApiMetadata(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.Builder.IEndpointConventionBuilder,Kestrun.Hosting.Options.OpenAPIMetadata)
AddHtmlTemplateRoute(Kestrun.Hosting.KestrunHost,System.String,System.String,System.Collections.Generic.List`1<System.String>)
AddHtmlTemplateRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.String)
AddSwaggerUiRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.Uri)
AddRedocUiRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.Uri)
AddScalarUiRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.Uri)
AddRapiDocUiRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.Uri)
AddElementsUiRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.Uri)
AddOpenApiUiRoute(Kestrun.Hosting.KestrunHost,Kestrun.Hosting.Options.MapRouteOptions,System.Uri,System.String,System.String,System.String)
AddHtmlRouteFromEmbeddedResource(Kestrun.Hosting.KestrunHost,System.String,System.Uri,System.String)
MapExists(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IEnumerable`1<Kestrun.Utilities.HttpVerb>)
MapExists(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb)
GetMapRouteOptions(Kestrun.Hosting.KestrunHost,System.String,Kestrun.Utilities.HttpVerb)
AddAntiforgeryTokenRoute(Kestrun.Hosting.KestrunHost,System.String)
IsUnsafeVerb(Kestrun.Utilities.HttpVerb)
IsUnsafeMethod(System.String)
ShouldValidateCsrf(Kestrun.Hosting.Options.MapRouteOptions,Microsoft.AspNetCore.Http.HttpContext)
TryValidateAntiforgeryAsync()