summaryrefslogtreecommitdiff
path: root/engine/request.php
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2024-01-31 06:11:00 +0300
committerEvgeny Zinoviev <me@ch1p.io>2024-01-31 20:45:40 +0300
commitc0dc531ebefd8912819f3b6c8bda1fed3c7e750c (patch)
tree2c75aa9df182260aef09faf4befd81a4c2b9c5e2 /engine/request.php
parent48d688cdf7f9eae1bf11b8a6f0e5b98687c604cb (diff)
make it simple, but not simpler
Diffstat (limited to 'engine/request.php')
-rw-r--r--engine/request.php203
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