diff --git a/src/bootstrap/requires.php b/src/bootstrap/requires.php index 9968e75..c83fd6d 100644 --- a/src/bootstrap/requires.php +++ b/src/bootstrap/requires.php @@ -20,6 +20,9 @@ final class requires { } public static function is_dangerous(string $file): bool { + // Remove non-visible characters + $file = preg_replace('/[\x00-\x1F\x7F]/u', '', $file); + if (strpos($file, "..") !== false) { return true; // .. Too dangerious, up path attack } @@ -68,7 +71,7 @@ final class requires { } $dir = str_replace('_DS_', '/', $dir); $dir = self::filter_dir_path($dir); - $dir = escapeshellcmd($dir); + $dir = escapeshellcmd(realpath($dir)); return (file_exists($dir)) ? $dir : false; } diff --git a/src/bootstrap/safer_io.php b/src/bootstrap/safer_io.php index 78907ce..af44ebe 100644 --- a/src/bootstrap/safer_io.php +++ b/src/bootstrap/safer_io.php @@ -78,8 +78,8 @@ final class use_iol { } final class safer_io { - private static $JSON_POST_DATA = []; - private static $DATA_INPUTS = []; + private static string $string_of_POST_data = ""; + private static array $DATA_INPUTS = []; protected function __construct() { @@ -96,6 +96,29 @@ final class safer_io { self::$DATA_INPUTS[$var_name] : null; } + public static function grab_all_post_data( + int $bytes_limit = 650000, + int $max_params = 400 + ): void { + if ($stream = fopen("php://input", 'r')) { + if ($bytes_limit === 0) { + $post_data = stream_get_contents($stream); + } else { + $post_data = stream_get_contents($stream, $bytes_limit); + } + + fclose($stream); + if ($bytes_limit > 0 && strlen($post_data) == $bytes_limit) { + throw new \Exception("Too much input data!"); + } + $count_params = substr_count($post_data, "&"); + if ($max_params > 0 && $count_params > $max_params) { + throw new \Exception("Too many input parameters!"); + } + self::$string_of_POST_data = $post_data; + } + } + public static function convert_to_utf8(string $in_str): string { if (! extension_loaded('mbstring')) { return $in_str; @@ -185,26 +208,49 @@ final class safer_io { /** * Purpose: To decode JQuery encoded objects, arrays, strings, int, bool types. * The content must be of application/json. - * Returns the JSON encoded POST data, if any.... - * @param type $return_as_array (true) -> Array, (false) -> Object - * @retval type Object/Array|null|false * Note: It will return null if not valid json. false is not application/json */ - public static function get_json_post_data(bool $return_as_array = true, int $levels_deep = 512): mixed { - $content_type = self::get_clean_server_var('CONTENT_TYPE'); - if ($content_type === null) { + private static function get_json_post_data( + string $input_field_name, + bool $return_as_array = true, + int $levels_deep = 512 + ) { + $ret_json = self::json_decode( + self::$string_of_POST_data, + $return_as_array, + $levels_deep + ); + if (self::has_json_error($ret_json)) { return false; } - if (str_contains($content_type, "application/json")) { - $post_body = trim(file_get_contents("php://input")); // get raw POST data. - $ret_json = self::json_decode($post_body, $return_as_array, $levels_deep); - if (self::has_json_error($ret_json)) { - return false; - } - return $ret_json; - } else { - return false; + if (isset($ret_json[$input_field_name])) { + return $ret_json[$input_field_name]; } + return false; + } + + private static function get_post_data(): \Generator { + $pairs = explode("&", self::$string_of_POST_data); + while(true) { + $pair = array_pop($pairs); + if ($pair === null) { + break; + } + $nv = explode("=", $pair); + $n = $nv[0] ?? false; + $v = $nv[1] ?? ""; + unset($nv); + if ($n === false || empty($n)) { + continue; + } + $cmd = (yield urldecode($n) => urldecode($v)); + if ($cmd == "stop") { + break; + } + } + unset($n); + unset($v); + unset($pairs); } private static function safer_html(string $input, HTML_FLAG $safety_level = HTML_FLAG::escape): string { @@ -246,6 +292,25 @@ final class safer_io { return $item; } + private static function find_post_field(string $input_field_name) { + $content_type = self::get_clean_server_var('CONTENT_TYPE'); + if ($content_type === null) { + return false; + } + if (str_contains($content_type, "application/json")) { + return self::get_json_post_data($input_field_name); + } + if (str_contains($content_type, "application/x-www-form-urlencoded")) { + $post = self::get_post_data(); + foreach($post as $key => $data) { + if ($key === $input_field_name) { + $post->send("stop"); // Break loop in Generator + return $data; + } + } + } + return false; + } private static function get_input_by_type( string $input_field_name, @@ -258,9 +323,11 @@ final class safer_io { return self::get_data_input($input_field_name); } if ($input_type == INPUTS::debugging) { - if (isset(self::$JSON_POST_DATA[$input_field_name])) { - return self::$JSON_POST_DATA[$input_field_name]; + $rd = self::find_post_field($input_field_name); + if ($rd !== false) { + return $rd; } + $is_set = filter_has_var(INPUT_POST, $input_field_name); if ($is_set) { return filter_input(INPUT_POST, $input_field_name); @@ -275,7 +342,11 @@ final class safer_io { return null; } if ($input_type === INPUTS::json) { - return (isset(self::$JSON_POST_DATA[$input_field_name])) ? self::$JSON_POST_DATA[$input_field_name] : null; + $rd = self::find_post_field($input_field_name); + if ($rd !== false) { + return $rd; + } + return null; } $resolve_input = $input_type->resolve(); $is_set = filter_has_var($resolve_input, $input_field_name); @@ -318,15 +389,6 @@ final class safer_io { return $data; } - /** - * Initialize JSON post data into static array, if used.... - * @param int $levels_deep are JSON Levels to use - */ - - public static function init_json(int $levels_deep = 512): void { - self::$JSON_POST_DATA = self::get_json_post_data(true, $levels_deep); - } - public static function required_fields_were_NOT_all_submitted(array $data): bool { $field = $data['name'] ?? false; $empty = $data['meta'][$field]['empty'] ?? true; @@ -341,6 +403,7 @@ final class safer_io { FIELD_FILTER $default_filter = FIELD_FILTER::raw_string, bool $trim = true, ) : array { + $meta = []; $meta['missing'] = []; $safer_data = ""; diff --git a/src/bootstrap/site_helper.php b/src/bootstrap/site_helper.php index 6bf6789..933c4b5 100644 --- a/src/bootstrap/site_helper.php +++ b/src/bootstrap/site_helper.php @@ -235,14 +235,15 @@ final class site_helper { } private static function set_params(): void { - // Get query string - $request = explode('?', self::$REQUEST_URI); - $queryParams = []; - if (isset($request[1])) { - $queryParams = $request[1]; - parse_str($queryParams, $queryParams); - self::$queryParams = $queryParams; + // Get just query string + $pos = strpos(self::$REQUEST_URI, "?"); + $uri = ($pos !== false) ? substr(self::$REQUEST_URI, $pos + 1) : ""; + if (empty($uri)) { + return; } + $queryParams = []; + parse_str($uri, $queryParams); + self::$queryParams = $queryParams; } public static function get_cli_args(): string { @@ -260,8 +261,10 @@ final class site_helper { } private static function set_route(): void { - $uri = explode('?', self::$REQUEST_URI); - $root = str_replace(self::$ROOT, "", $uri[0]); + // Get just route + $pos = strpos(self::$REQUEST_URI, "?"); + $uri = ($pos !== false) ? substr(self::$REQUEST_URI, 0, $pos) : self::$REQUEST_URI; + $root = str_replace(self::$ROOT, "", $uri); $routes = explode('/', trim($root, '/')); $project = $routes[0] ?? ""; if (self::set_project($project)) { diff --git a/src/classes/services/cache/file.php b/src/classes/services/cache/file.php index 1fe4e7b..c90ee14 100644 --- a/src/classes/services/cache/file.php +++ b/src/classes/services/cache/file.php @@ -72,7 +72,9 @@ class file { fclose($handle); - return unserialize($data); + // unserialize is dangerious, could pass ['allowed_classes' => false] + + return json_decode($data, true); } return false; @@ -91,7 +93,7 @@ class file { flock($handle, LOCK_EX); - fwrite($handle, serialize($value)); + fwrite($handle, json_encode($value)); fflush($handle);