ch1p_io_web/engine/request.php
2024-01-31 20:45:40 +03:00

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);
}