From 2ce64602262681e500e6ae713016d14409163db8 Mon Sep 17 00:00:00 2001 From: jalexiscv Date: Sun, 17 May 2026 22:47:29 -0500 Subject: [PATCH] fix: prevent subdomain-only route from overwriting hostname route Fixes #7214 When routes with both 'hostname' and 'subdomain' options are defined separately, a less specific subdomain-only route could overwrite a more specific hostname-matched route due to registration order. Changes: - Change 'elseif' to 'if' so both hostname and subdomain checks are independent. A route with both options now validates both. - Track matchedByHostname/matchedBySubdomain flags during registration - Add guard to prevent a route matched only by subdomain from overwriting an existing route that was registered with hostname, since hostname specificity should take precedence. Ref: https://github.com/codeigniter4/CodeIgniter4/issues/7214 --- system/Router/RouteCollection.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 3c08958604d2..65477130db26 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -1475,23 +1475,29 @@ protected function create(string $verb, string $from, $to, ?array $options = nul } // Hostname limiting? + $matchedByHostname = false; + if (! empty($options['hostname'])) { // @todo determine if there's a way to whitelist hosts? if (! $this->checkHostname($options['hostname'])) { return; } - $overwrite = true; + $overwrite = true; + $matchedByHostname = true; } // Limiting to subdomains? - elseif (! empty($options['subdomain'])) { + $matchedBySubdomain = false; + + if (! empty($options['subdomain'])) { // If we don't match the current subdomain, then // we don't need to add the route. if (! $this->checkSubdomains($options['subdomain'])) { return; } - $overwrite = true; + $overwrite = true; + $matchedBySubdomain = true; } // Are we offsetting the binds? @@ -1541,6 +1547,17 @@ protected function create(string $verb, string $from, $to, ?array $options = nul // this works only because discovered routes are added just prior // to attempting to route the request. $routeKeyExists = isset($this->routes[$verb][$routeKey]); + + // Prevent a subdomain-only route from overwriting a hostname-matched route, + // since hostname is more specific than subdomain. + if ($routeKeyExists && $matchedBySubdomain && ! $matchedByHostname) { + $existingOptions = $this->routesOptions[$verb][$routeKey] ?? []; + + if (! empty($existingOptions['hostname'])) { + return; + } + } + if ((isset($this->routesNames[$verb][$name]) || $routeKeyExists) && ! $overwrite) { return; }