203 lines
6.5 KiB
PHP
203 lines
6.5 KiB
PHP
<?php
|
|
|
|
function dispatch_request(): void {
|
|
global $RouterInput;
|
|
|
|
if (!in_array($_SERVER['REQUEST_METHOD'], ['POST', 'GET']))
|
|
http_error(HTTPCode::NotImplemented, 'Method '.$_SERVER['REQUEST_METHOD'].' not implemented');
|
|
|
|
$route = router_find(request_path());
|
|
if ($route === null)
|
|
http_error(HTTPCode::NotFound, 'Route not found');
|
|
|
|
$route = preg_split('/ +/', $route);
|
|
$handler_class = $route[0].'Handler';
|
|
if (!class_exists($handler_class))
|
|
http_error(HTTPCode::NotFound, is_dev() ? 'Handler class "'.$handler_class.'" not found' : '');
|
|
|
|
$action = $route[1];
|
|
|
|
if (count($route) > 2) {
|
|
for ($i = 2; $i < count($route); $i++) {
|
|
$var = $route[$i];
|
|
list($k, $v) = explode('=', $var);
|
|
$RouterInput[trim($k)] = trim($v);
|
|
}
|
|
}
|
|
|
|
/** @var request_handler $handler */
|
|
$handler = new $handler_class();
|
|
$handler->call_act($_SERVER['REQUEST_METHOD'], $action);
|
|
}
|
|
|
|
function request_path(): string {
|
|
$uri = $_SERVER['REQUEST_URI'];
|
|
if (($pos = strpos($uri, '?')) !== false)
|
|
$uri = substr($uri, 0, $pos);
|
|
return $uri;
|
|
}
|
|
|
|
|
|
enum HTTPCode: int {
|
|
case MovedPermanently = 301;
|
|
case Found = 302;
|
|
|
|
case Unauthorized = 401;
|
|
case NotFound = 404;
|
|
case Forbidden = 403;
|
|
|
|
case InternalServerError = 500;
|
|
case NotImplemented = 501;
|
|
}
|
|
|
|
function http_error(HTTPCode $http_code, string $message = ''): void {
|
|
$ctx = new SkinContext('\\skin\\error');
|
|
$http_message = preg_replace('/(?<!^)([A-Z])/', ' $1', $http_code->name);
|
|
$html = $ctx->http_error($http_code->value, $http_message, $message);
|
|
http_response_code($http_code->value);
|
|
echo $html;
|
|
exit;
|
|
}
|
|
|
|
function redirect(string $url, HTTPCode $code = HTTPCode::MovedPermanently): void {
|
|
if (!in_array($code, [HTTPCode::MovedPermanently, HTTPCode::Found]))
|
|
internal_server_error('invalid http code');
|
|
http_response_code($code->value);
|
|
header('Location: '.$url);
|
|
exit;
|
|
}
|
|
|
|
function internal_server_error(string $message = '') { http_error(HTTPCode::InternalServerError, $message); }
|
|
function not_found(string $message = '') { http_error(HTTPCode::NotFound, $message); }
|
|
function forbidden(string $message = '') { http_error(HTTPCode::Forbidden, $message); }
|
|
function is_xhr_request(): bool { return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'; }
|
|
function ajax_ok(mixed $data): void { ajax_response(['response' => $data]); }
|
|
function ajax_error(mixed $error, int $code = 200): void { ajax_response(['error' => $error], $code); }
|
|
|
|
function ajax_response(mixed $data, int $code = 200): void {
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
header('Pragma: no-cache');
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
http_response_code($code);
|
|
echo jsonEncode($data);
|
|
exit;
|
|
}
|
|
|
|
|
|
abstract class request_handler {
|
|
function __construct() {
|
|
add_static(
|
|
'css/common.css',
|
|
'js/common.js'
|
|
);
|
|
add_skin_strings_re('/^theme_/');
|
|
}
|
|
|
|
function before_dispatch(string $http_method, string $action) {}
|
|
|
|
function call_act(string $http_method, string $action, array $input = []) {
|
|
global $RouterInput;
|
|
|
|
$handler_method = $_SERVER['REQUEST_METHOD'].'_'.$action;
|
|
if (!method_exists($this, $handler_method))
|
|
not_found(static::class.'::'.$handler_method.' is not defined');
|
|
|
|
if (!((new ReflectionMethod($this, $handler_method))->isPublic()))
|
|
not_found(static::class.'::'.$handler_method.' is not public');
|
|
|
|
if (!empty($input)) {
|
|
foreach ($input as $k => $v)
|
|
$RouterInput[$k] = $v;
|
|
}
|
|
|
|
$args = $this->before_dispatch($http_method, $action);
|
|
return call_user_func_array([$this, $handler_method], is_array($args) ? [$args] : []);
|
|
}
|
|
}
|
|
|
|
|
|
enum InputVarType: string {
|
|
case INTEGER = 'i';
|
|
case FLOAT = 'f';
|
|
case BOOLEAN = 'b';
|
|
case STRING = 's';
|
|
case ENUM = 'e';
|
|
}
|
|
|
|
function input(string $input): array {
|
|
global $RouterInput;
|
|
$input = preg_split('/,\s+?/', $input, -1, PREG_SPLIT_NO_EMPTY);
|
|
$ret = [];
|
|
foreach ($input as $var) {
|
|
$enum_values = null;
|
|
$enum_default = null;
|
|
|
|
$pos = strpos($var, ':');
|
|
if ($pos !== false) {
|
|
$type = substr($var, 0, $pos);
|
|
$rest = substr($var, $pos + 1);
|
|
|
|
$vartype = InputVarType::tryFrom($type);
|
|
if (is_null($vartype))
|
|
internal_server_error('invalid input type '.$type);
|
|
|
|
if ($vartype == InputVarType::ENUM) {
|
|
$br_from = strpos($rest, '(');
|
|
$br_to = strpos($rest, ')');
|
|
|
|
if ($br_from === false || $br_to === false)
|
|
internal_server_error('failed to parse enum values: '.$rest);
|
|
|
|
$enum_values = array_map('trim', explode('|', trim(substr($rest, $br_from + 1, $br_to - $br_from - 1))));
|
|
$name = trim(substr($rest, 0, $br_from));
|
|
|
|
if (!empty($enum_values)) {
|
|
foreach ($enum_values as $key => $val) {
|
|
if (str_starts_with($val, '=')) {
|
|
$enum_values[$key] = substr($val, 1);
|
|
$enum_default = $enum_values[$key];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$name = trim($rest);
|
|
}
|
|
|
|
} else {
|
|
$vartype = InputVarType::STRING;
|
|
$name = trim($var);
|
|
}
|
|
|
|
$val = null;
|
|
if (isset($RouterInput[$name])) {
|
|
$val = $RouterInput[$name];
|
|
} else if (isset($_POST[$name])) {
|
|
$val = $_POST[$name];
|
|
} else if (isset($_GET[$name])) {
|
|
$val = $_GET[$name];
|
|
}
|
|
if (is_array($val))
|
|
$val = implode($val);
|
|
|
|
$ret[] = match($vartype) {
|
|
InputVarType::INTEGER => (int)$val,
|
|
InputVarType::FLOAT => (float)$val,
|
|
InputVarType::BOOLEAN => (bool)$val,
|
|
InputVarType::ENUM => !in_array($val, $enum_values) ? $enum_default ?? '' : (string)$val,
|
|
default => (string)$val
|
|
};
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function csrf_get(string $key): string { return _csrf_get_token($_SERVER['REMOTE_ADDR'], $key); }
|
|
function csrf_check(string $key) {
|
|
if (csrf_get($key) != ($_REQUEST['token'] ?? '')) {
|
|
forbidden('invalid token');
|
|
}
|
|
}
|
|
|
|
function _csrf_get_token(string $user_token, string $key): string {
|
|
global $config;
|
|
return substr(sha1($config['csrf_token'].$user_token.$key), 0, 20);
|
|
} |