|
|
|
@@ -0,0 +1,98 @@
|
|
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Parse request URI
|
|
|
|
|
// Examples:
|
|
|
|
|
// / => /
|
|
|
|
|
// /about => /about
|
|
|
|
|
// /about/ => /about
|
|
|
|
|
// /about?id=1 => /about
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
|
|
|
|
|
|
|
|
|
if (!is_string($uri)) {
|
|
|
|
|
http_response_code(400);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$uri = rtrim($uri, '/');
|
|
|
|
|
$uri = $uri ?: '/';
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Request method
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Load route configuration
|
|
|
|
|
// config/routes.php:
|
|
|
|
|
// return [
|
|
|
|
|
// 'GET' => [
|
|
|
|
|
// '/' => 'home.php',
|
|
|
|
|
// '/about' => 'about.php',
|
|
|
|
|
// ],
|
|
|
|
|
// ];
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
$routes = require ROOT . '/config/routes.php';
|
|
|
|
|
|
|
|
|
|
if (!is_array($routes)) {
|
|
|
|
|
error_log('Router: routes.php must return an array');
|
|
|
|
|
http_response_code(500);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Find matching route
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
$route = $routes[$method][$uri] ?? null;
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Base directory containing all page files
|
|
|
|
|
// Every routed file MUST live inside this directory.
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
$base = realpath(ROOT . '/src/views/pages');
|
|
|
|
|
|
|
|
|
|
if ($base === false) {
|
|
|
|
|
error_log('Router: pages directory not found');
|
|
|
|
|
http_response_code(500);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Route not found
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
if ($route === null) {
|
|
|
|
|
http_response_code(404);
|
|
|
|
|
$route = '404.php';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Resolve requested file
|
|
|
|
|
// realpath() converts:
|
|
|
|
|
// home.php
|
|
|
|
|
// ../home.php
|
|
|
|
|
// ../../etc/passwd
|
|
|
|
|
// into an absolute canonical path.
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
$real = realpath($base . '/' . $route);
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Path traversal protection
|
|
|
|
|
// Only allow files that resolve inside:
|
|
|
|
|
// src/views/pages/
|
|
|
|
|
//
|
|
|
|
|
// This blocks attempts such as:
|
|
|
|
|
// ../../etc/passwd
|
|
|
|
|
// ../../../secret.php
|
|
|
|
|
// even if they somehow end up in the route table.
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
if ($real === false || !str_starts_with($real, $base . DIRECTORY_SEPARATOR)) {
|
|
|
|
|
error_log("Router: invalid route target [$route]");
|
|
|
|
|
http_response_code(403);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Dispatch request
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
require $real;
|