You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
6.2 KiB
185 lines
6.2 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* @author Robert Strutts <Robert@TryingToScale.com>
|
|
* @copyright Copyright (c) 2022, Robert Strutts.
|
|
* @license MIT
|
|
*/
|
|
|
|
namespace bs_tts;
|
|
|
|
final class requires {
|
|
|
|
public static function is_valid_file(string $filename): bool {
|
|
if (is_string($filename) && strlen($filename) < 64) {
|
|
return (self::is_dangerous($filename) === false) ? true : false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static function is_dangerous(string $file): bool {
|
|
// Make sure the file does not contain null bytes to avoid PHAR file attacks
|
|
if (strpos($file, "\x00") !== false) {
|
|
return true;
|
|
}
|
|
|
|
// Remove non-visible characters
|
|
$file = preg_replace('/[\x00-\x1F\x7F]/u', '', $file);
|
|
|
|
if (strpos($file, "..") !== false || strpos($file, "./") !== false) {
|
|
return true; // .. Too dangerious, up path attack
|
|
}
|
|
|
|
/*
|
|
* :// Too dangerious, PHAR file execution of serialized code injection, etc...
|
|
* Also, prevent remote code execution from http://, ftp://
|
|
*/
|
|
if (strpos($file, "://") !== false) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static function filter_file_name(string $file): string {
|
|
return preg_replace('/[^-a-z0-9._\\/\s]+/i', "", $file);
|
|
}
|
|
|
|
/**
|
|
* Do not allow periods in folder names!
|
|
* @param string $dir
|
|
* @return string
|
|
*/
|
|
public static function filter_dir_path(string $dir): string {
|
|
return preg_replace('/[^a-z0-9_\\/]+/i', "", $dir);
|
|
}
|
|
|
|
private static function get_PHP_Version_for_file(string $file): string|bool {
|
|
if (file_exists($file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION) && is_readable($file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION)) {
|
|
return $file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION;
|
|
} elseif (file_exists($file . PHP_MAJOR_VERSION) && is_readable($file . PHP_MAJOR_VERSION)) {
|
|
return $file . PHP_MAJOR_VERSION;
|
|
} elseif (file_exists($file) && is_readable($file)) {
|
|
return $file;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Cleaned DIR or False
|
|
*/
|
|
public static function safer_dir_exists(string $dir): string|bool {
|
|
if (self::is_dangerous($dir) || empty($dir)) {
|
|
return false;
|
|
}
|
|
$dir = str_replace('_DS_', '/', $dir);
|
|
$dir = self::filter_dir_path($dir);
|
|
|
|
$realpath = realpath($dir);
|
|
if ($realpath === false) {
|
|
return false;
|
|
}
|
|
|
|
$dir = escapeshellcmd($realpath);
|
|
return (file_exists($dir)) ? $dir : false;
|
|
}
|
|
|
|
/**
|
|
* @return Cleaned FileName or False
|
|
*/
|
|
public static function safer_file_exists(string $file, string $dir=""): string|bool {
|
|
if (self::is_dangerous($file)) {
|
|
return false;
|
|
}
|
|
if (self::is_dangerous($dir)) {
|
|
return false;
|
|
}
|
|
if (empty($file)) {
|
|
return false;
|
|
}
|
|
|
|
$pos = \bs_tts\common::string_last_position($file, ".");
|
|
if ($pos === false) {
|
|
$file_type = ".php";
|
|
$file_name = $file;
|
|
} else {
|
|
$file_name = \bs_tts\common::get_string_left($file, $pos);
|
|
$file_kind = \bs_tts\common::get_string_right($file, \bs_tts\common::string_length($file) - $pos);
|
|
$file_type = match ($file_kind) {
|
|
".tpl" => ".tpl",
|
|
default => ".php",
|
|
};
|
|
}
|
|
|
|
$filtered_file = self::filter_file_name($file_name);
|
|
if (empty($dir)) {
|
|
$file_plus_dir = $filtered_file;
|
|
} else {
|
|
$filtered_dir = rtrim(self::filter_dir_path($dir), '/') . '/';
|
|
$file_plus_dir = $filtered_dir . $filtered_file;
|
|
}
|
|
$escaped_file = escapeshellcmd($file_plus_dir . $file_type);
|
|
return self::get_PHP_Version_for_file($escaped_file);
|
|
}
|
|
|
|
private static function ob_starter() {
|
|
if (extension_loaded('mbstring')) {
|
|
ob_start('mb_output_handler');
|
|
} else {
|
|
ob_start();
|
|
}
|
|
}
|
|
|
|
private static function secure_file(bool $return_contents, string $file, string $path, $local = null, array $args = array(), bool $load_once = true) {
|
|
$dir = match ($path) {
|
|
"dir_fixed" => "",
|
|
"framework", "tts" => \main_tts\TTS_FRAMEWORK,
|
|
"on_error" => \bs_tts\site_helper::get_root() . \bs_tts\site_helper::get_project() . "views/on_error/",
|
|
default => \bs_tts\site_helper::get_root(), // project
|
|
};
|
|
$versioned_file = self::safer_file_exists($file, $dir);
|
|
if ($versioned_file === false) {
|
|
return false;
|
|
}
|
|
|
|
if (is_array($args)) {
|
|
if (isset($args["return_contents"])
|
|
|| isset($args["script_output"])
|
|
|| isset($args["versioned_file"])
|
|
) {
|
|
return false; // Args are Dangerious!! Abort
|
|
}
|
|
extract($args, EXTR_PREFIX_SAME, "dup");
|
|
}
|
|
|
|
if ($return_contents) {
|
|
$script_output = (string) ob_get_clean();
|
|
self::ob_starter();
|
|
include $versioned_file;
|
|
$script_output .= (string) ob_get_clean();
|
|
return $script_output;
|
|
} else {
|
|
return ($load_once) ? include_once($versioned_file) : include($versioned_file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use secure_include instead of include.
|
|
* @param string $file script
|
|
* @param string $path (framework or project)
|
|
* @param type $local ($this)
|
|
* @param array $args ($var to pass along)
|
|
* @param bool $load_once (default true)
|
|
* @return results of include or false if failed
|
|
*/
|
|
public static function secure_include(string $file, string $path = 'project', $local = null, array $args = array(), bool $load_once = true) {
|
|
return self::secure_file(false, $file, $path, $local, $args, $load_once);
|
|
}
|
|
|
|
public static function secure_get_content(string $file, string $path = 'project', $local = null, array $args = array(), bool $load_once = true) {
|
|
return self::secure_file(true, $file, $path, $local, $args, $load_once);
|
|
}
|
|
|
|
}
|
|
|