diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2024-01-31 06:11:00 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2024-01-31 20:45:40 +0300 |
commit | c0dc531ebefd8912819f3b6c8bda1fed3c7e750c (patch) | |
tree | 2c75aa9df182260aef09faf4befd81a4c2b9c5e2 /engine/request.php | |
parent | 48d688cdf7f9eae1bf11b8a6f0e5b98687c604cb (diff) |
make it simple, but not simpler
Diffstat (limited to 'engine/request.php')
-rw-r--r-- | engine/request.php | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/engine/request.php b/engine/request.php new file mode 100644 index 0000000..e67d4fb --- /dev/null +++ b/engine/request.php @@ -0,0 +1,203 @@ +<?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); +}
\ No newline at end of file |