From c0dc531ebefd8912819f3b6c8bda1fed3c7e750c Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Wed, 31 Jan 2024 06:11:00 +0300 Subject: make it simple, but not simpler --- engine/request.php | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 engine/request.php (limited to 'engine/request.php') 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 @@ + 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('/(?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 -- cgit v1.2.3