diff --git a/lib/Controller/ExAppProxyController.php b/lib/Controller/ExAppProxyController.php index b390607a..273d27a7 100644 --- a/lib/Controller/ExAppProxyController.php +++ b/lib/Controller/ExAppProxyController.php @@ -319,6 +319,7 @@ private function buildMultipartFormData(array $bodyParams, array $files): array private function passesExAppProxyRoutesChecks(ExApp $exApp, string $exAppRoute): array { // Route URL is a regex matched against the request path including its leading slash, mirroring HaRP's target_path semantics. $canonicalSubject = '/' . $exAppRoute; + $method = strtoupper($this->request->getMethod()); foreach ($exApp->getRoutes() as $route) { $pattern = '~^(?:' . str_replace('~', '\\~', $route['url']) . ')~i'; $matched = preg_match($pattern, $canonicalSubject) === 1; @@ -330,9 +331,7 @@ private function passesExAppProxyRoutesChecks(ExApp $exApp, string $exAppRoute): )); $matched = true; } - if ($matched && - str_contains(strtolower($route['verb']), strtolower($this->request->getMethod())) - ) { + if ($matched && self::routeAcceptsMethod($route['verb'], $method)) { // First match by path+verb wins. Apply its access level without falling through to broader routes. return $this->passesExAppProxyRouteAccessLevelCheck($route['access_level']) ? $route : []; } @@ -340,6 +339,14 @@ private function passesExAppProxyRoutesChecks(ExApp $exApp, string $exAppRoute): return []; } + /** + * As in HaRP: the verb lists methods separated by commas or whitespace, and HEAD is accepted wherever GET is. + */ + private static function routeAcceptsMethod(string $verb, string $method): bool { + $methods = array_map('strtoupper', preg_split('/[\s,]+/', $verb, -1, PREG_SPLIT_NO_EMPTY) ?: []); + return in_array($method, $methods, true) || ($method === 'HEAD' && in_array('GET', $methods, true)); + } + private function passesExAppProxyRouteAccessLevelCheck(int $accessLevel): bool { return match ($accessLevel) { ExAppRouteAccessLevel::PUBLIC->value => true,