diff --git a/src/bootstrap/errors.php b/src/bootstrap/errors.php index c18bd9b..35d50ef 100644 --- a/src/bootstrap/errors.php +++ b/src/bootstrap/errors.php @@ -32,11 +32,11 @@ function tts_broken_error($ex = ''): void { \tts\api::error(array('response' => $internal_error, 'code' => 500, 'reason' => 'Internal Error')); } - if (\main_tts\configure::get('tts', 'live') === true) { + if (\main_tts\is_live()) { $resource = \main_tts\configure::get('tts', 'error_page'); $exists = \bs_tts\requires::secure_include('views/errors', $resource); // Show Broken Page if ($exists === false) { - echo "Sorry, we had an error...We apologize for any inconvenience this may cause."; + echo "

Sorry, we had an error...

We apologize for any inconvenience this may cause.

"; } } else { $view = new \tts\view(); @@ -211,7 +211,7 @@ function tts_json_error_handler($data) { } catch (Exception $e) { } - if (\main_tts\configure::get('tts', 'live') === true) { + if (\main_tts\is_live()) { \tts_email_error($data); } @@ -251,6 +251,10 @@ function tts_json_error_handler($data) { * @param \Throwable $exception Error */ function tts_exception_handler(\Throwable $exception) { + if (!\main_tts\errors::get_handle_exceptions()) { + return; + } + $err = "Fatal Error: Uncaught exception " . get_class($exception) . " with message: " . $exception->getMessage(); $err .= " thrown in: " . $exception->getFile() . " on line: " . $exception->getLine() . "\r\n"; $common_err = 'Fatal error: Uncaught exception \'' . get_class($exception) . '\' with message '; @@ -266,7 +270,7 @@ function tts_exception_handler(\Throwable $exception) { $msg .= $common_err; $msg .= ''; - if (\main_tts\configure::get('tts', 'live') === true) { + if (\main_tts\is_live()) { tts_global_error_handler(E_USER_ERROR, $err); } else { echo tts_mini_view('dev_error', 'tts', $msg); @@ -276,7 +280,7 @@ function tts_exception_handler(\Throwable $exception) { set_exception_handler('tts_exception_handler'); -if (\main_tts\configure::get('tts', 'live') === true) { +if (\main_tts\is_live()) { error_reporting(E_ALL ^ E_NOTICE); set_error_handler('tts_global_error_handler', E_ALL ^ (E_NOTICE | E_USER_NOTICE)); } else { @@ -289,10 +293,13 @@ register_shutdown_function('tts_custom_error_checker'); * Custom Error Checked called on Script Shutdown. */ function tts_custom_error_checker(): void { + if (!\main_tts\errors::get_handle_shutdown_errors()) { + return; + } $a_errors = error_get_last(); if (is_array($a_errors)) { $msg = "Error: {$a_errors['message']} File:{$a_errors['file']} Line:{$a_errors['line']}."; - if (\main_tts\configure::get('tts', 'live') === true) { + if (\main_tts\is_live()) { $msg = wordwrap($msg, WORD_WRAP_CHRS, "
\n"); try { $exists = \main_tts\registry::get('di')->exists('log'); @@ -327,6 +334,10 @@ function tts_custom_error_checker(): void { * @param type $errline */ function tts_global_error_handler(int $errno = 0, string $errstr = '', string $errfile = '', int $errline = 0) { + if (!\main_tts\errors::get_handle_global_errors()) { + return; + } + switch ($errno) { case E_USER_ERROR: $err = "My ERROR [$errno] $errstr
\n"; @@ -352,7 +363,7 @@ function tts_global_error_handler(int $errno = 0, string $errstr = '', string $e tts_json_error_handler($err); - if (\main_tts\configure::get('tts', 'live') === false) { + if (! \main_tts\is_live()) { return true; } diff --git a/src/bootstrap/safer_io.php b/src/bootstrap/safer_io.php index 1724821..d861292 100644 --- a/src/bootstrap/safer_io.php +++ b/src/bootstrap/safer_io.php @@ -139,10 +139,23 @@ final class use_iol { } final class safer_io { + private static $JSON_POST_DATA = []; + private static $DATA_INPUTS = []; protected function __construct() { } + // Allow anything to set_data_inputs is desired here + public static function set_data_input(string $var_name, $data_in): void { + if (! isset(self::$DATA_INPUTS[$var_name])) { + self::$DATA_INPUTS[$var_name] = $data_in; + } + } + // Do not allow anyone out-side of this class to get this un-filtered input + private static function get_data_input(string $var_name) { + return (isset(self::$DATA_INPUTS[$var_name])) ? + self::$DATA_INPUTS[$var_name] : null; + } public static function convert_to_utf8(string $in_str): string { if (! extension_loaded('mbstring')) { @@ -294,12 +307,17 @@ final class safer_io { return $item; } - static $JSON_POST_DATA = []; private static function get_input_by_type( string $input_field_name, INPUTS $input_type, ): mixed { + /* Must return here to avoid Failing Resolve later on in this FN + * as input types variable, debugging, and json will not Resolve! + */ + if ($input_type == INPUTS::variable) { + 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]; diff --git a/src/bootstrap/site_helper.php b/src/bootstrap/site_helper.php index d76ef4e..4e8ac4c 100644 --- a/src/bootstrap/site_helper.php +++ b/src/bootstrap/site_helper.php @@ -21,11 +21,60 @@ final class site_helper { private static $queryParams; private static $DEFAULT_PROJECT; private static $all_projects = []; + private static $local_site_domains = ['localhost']; + private static $Private_IPs_allowed = ['127.0.0.1', '::1']; + private static $Public_IPs_allowed = []; public static function set_all_projects(array $projects): void { self::$all_projects = $projects; } + public static function set_local_site_domains(string|array $domain_name): void { + if (is_array($domain_name)) { + foreach($domain_name as $domain) { + self::$local_site_domains[] = $domain; + } + } elseif (is_string($domain_name)) { + self::$local_site_domains[] = $domain_name; + } + } + + public static function set_allowed_Private_IPs(string|array $IP_addresses): void { + if (is_array($IP_addresses)) { + foreach($IP_addresses as $IP) { + $s_ip = \tts\security::get_valid_ip($IP); + if ($s_ip === false) { + continue; + } + self::$Private_IPs_allowed[] = $IP; + } + } elseif (is_string($IP_addresses)) { + $s_ip = \tts\security::get_valid_ip($IP); + if ($s_ip === false) { + return; + } + self::$Private_IPs_allowed[] = $IP_addresses; + } + } + + public static function set_allowed_Public_IPs(string|array $IP_addresses): void { + if (is_array($IP_addresses)) { + foreach($IP_addresses as $IP) { + $s_ip = \tts\security::get_valid_public_ip($IP); + if ($s_ip === false) { + continue; + } + self::$Public_IPs_allowed[] = $s_ip; + } + } elseif (is_string($IP_addresses)) { + $s_ip = \tts\security::get_valid_public_ip($IP); + if ($s_ip === false) { + return; + } + self::$Public_IPs_allowed[] = $IP_addresses; + } + } + public static function get_root(): string { return self::$ROOT; } @@ -58,13 +107,33 @@ final class site_helper { self::$DEFAULT_PROJECT = $project; } + public static function is_server_name_a_private_domain(): bool { + $white_list = array_merge(self::$local_site_domains, self::$Private_IPs_allowed); + return (\tts\security::is_server_name_on_domain_list($white_list)); + } + + public static function remote_not_allowed_force_live(): bool { + return (! self::is_allowed()); + } + + public static function is_allowed(): bool { + $remote_ip = \tts\security::get_client_ip_address(); + if (in_array($remote_ip, self::$Public_IPs_allowed)) { + return true; + } + if (self::is_server_name_a_private_domain() && in_array($remote_ip, self::$Private_IPs_allowed)) { + return true; + } + return false; + } + /** * Because $_SERVER['REQUEST_URI'] May only available on Apache, * we generate an equivalent using other environment variables. * @return string */ public static function tts_request_uri() { - if (!empty(self::$REQUEST_URI) && self::$REQUEST_URI !== null) { + if (self::$REQUEST_URI !== null && !empty(self::$REQUEST_URI)) { $uri = self::$REQUEST_URI; } else if (isset($_SERVER['REQUEST_URI'])) { $uri = \bs_tts\safer_io::get_clean_server_var('REQUEST_URI'); diff --git a/src/classes/app.php b/src/classes/app.php index 0d7a079..aa84022 100644 --- a/src/classes/app.php +++ b/src/classes/app.php @@ -81,7 +81,11 @@ class app { $uri = $route; } - $filtered_uri = \tts\security::filter_uri($uri); + try { + $filtered_uri = \tts\security::filter_uri($uri); + } catch (Exception $ex) { + $this->local404(); // Route Un-Safe URI to Local 404 Page + } $safe_folders = \bs_tts\requires::filter_dir_path( $this->get_first_chunks($filtered_uri) @@ -138,13 +142,7 @@ class app { private function local404() { \tts\page_not_found::tts_error404(); } - - private function handle_my_errors($controller, string $method_name): void { -// if (! method_exists($controller, "skip_default_error_handler_for_" . $method_name)) { -// require_once \main_tts\TTS_FRAMEWORK . 'bootstrap/errors.php'; -// } - } - + /** * Do controller action * @param string $file @@ -175,18 +173,15 @@ class app { } $method .= "_api"; if (method_exists($controller, $method)) { - $this->handle_my_errors($controller, $method); return $controller->$method($params); } else { \tts\page_not_found::tts_error404_cli(); } } else { if (!empty($method) && method_exists($controller, $method)) { - $this->handle_my_errors($controller, $method); return $controller->$method($params); } else { if (empty($method) && method_exists($controller, 'index')) { - $this->handle_my_errors($controller, 'index'); return $controller->index($params); } else { $this->local404(); diff --git a/src/classes/assets.php b/src/classes/assets.php index bb18456..e8ecdc8 100644 --- a/src/classes/assets.php +++ b/src/classes/assets.php @@ -215,7 +215,7 @@ final class assets { $safe_file = \tts\security::filter_uri($file); if (\bs_tts\common::is_string_found($safe_file, '.auto.js')) { - $production = (\main_tts\configure::get('tts', 'live') === true) ? true : false; + $production = (\main_tts\is_live()); $safe_file = str_replace('.auto.js', '', $safe_file); if ( $production && file_exists($safe_path . $safe_file . '.min.js') ) { return $safe_file . '.min.js'; @@ -224,7 +224,7 @@ final class assets { } if (\bs_tts\common::is_string_found($safe_file, '.auto.css')) { - $production = (\main_tts\configure::get('tts', 'live') === true) ? true : false; + $production = (\main_tts\is_live()); $safe_file = str_replace('.auto.css', '', $safe_file); if ( $production && file_exists($safe_path . $safe_file . '.min.css') ) { return $safe_file . '.min.css'; diff --git a/src/classes/database/help_save.php b/src/classes/database/help_save.php index b46418e..5a45d74 100644 --- a/src/classes/database/help_save.php +++ b/src/classes/database/help_save.php @@ -126,7 +126,7 @@ final class help_save { * the DB security and Inject stuff into the DB via POST, etc... */ public function auto_set_members(array $post = [], array $skip = ['route', 'm'], array $only_these = []): void { - if (\main_tts\configure::get('tts', 'live') === true) { + if (\main_tts\is_live()) { return; // Bail if LIVE } if (isset($post['json'])) { diff --git a/src/classes/exceptions/DB_Exception.php b/src/classes/exceptions/DB_Exception.php index dec3e8a..fd295da 100644 --- a/src/classes/exceptions/DB_Exception.php +++ b/src/classes/exceptions/DB_Exception.php @@ -53,7 +53,7 @@ class DB_Exception extends \Exception { if (empty($message)) { $message = self::$error_message; } - $live = \bs_tts\common::get_bool( \main_tts\configure::get('tts', 'live') ); + $live = (\main_tts\is_live()); if ($live === false || \bs_tts\common::has_user_right('debugger')) { $msg = ($debug == DEBUGGER::basic || (self::$debug === DEBUGGER::basic && diff --git a/src/classes/page_not_found.php b/src/classes/page_not_found.php index c306c9b..c7ca65a 100644 --- a/src/classes/page_not_found.php +++ b/src/classes/page_not_found.php @@ -48,17 +48,9 @@ class page_not_found { self::tts_api_method_not_found(); } - $resource = \main_tts\configure::get('tts', '404_page'); - - if ($resource === null) { - echo "404 Page Not Found!"; - exit(1); - } - - $loaded = \bs_tts\requires::secure_include('views/404', $resource); // Show 404, Page Not Found Error Page! - + $loaded = \bs_tts\requires::secure_include('views/404', 'tts'); // Show 404, Page Not Found Error Page! if ($loaded === false) { - echo "404 Page Not Found!"; + echo "

404 Page Not Found!

"; } exit(1); diff --git a/src/classes/security.php b/src/classes/security.php index 141bb9c..5670280 100644 --- a/src/classes/security.php +++ b/src/classes/security.php @@ -156,28 +156,23 @@ final class security { public static function id_hash(): string { return crc32($_SESSION['user_id']); } + + public static function get_valid_ip(string $ip) { + return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6)); + } + public static function get_valid_public_ip(string $ip) { + return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_PRIV_RANGE)); + } /** - * LocalHost / Private IP - Check - * @return bool is LocalHost + * Is the server on the local test domain name + * @return bool SERVER Domain name is on whitelist */ - public static function is_localhost(): bool { - $whitelist = array('127.0.0.1', '::1', 'localhost', 'dev'); + public static function is_server_name_on_domain_list(array $whitelist): bool { if (!isset($_SERVER['SERVER_NAME'])) { return false; } - - if (\bs_tts\common::get_bool(\main_tts\configure::get('important', 'private_ip_as_local')) === true) { - if (!filter_var($_SERVER['SERVER_NAME'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE)) { // | FILTER_FLAG_NO_RES_RANGE - return true; // Private IP - } - } - - if (in_array($_SERVER['SERVER_NAME'], $whitelist)) { - return true; - } else { - return false; - } + return (in_array($_SERVER['SERVER_NAME'], $whitelist)); } /** @@ -185,22 +180,23 @@ final class security { * @return bool */ public static function request_is_same_domain(): bool { - if (self::is_localhost()) { - return true; - } - if (!isset($_SERVER['HTTP_REFERER'])) { // No referer send, so can't be same domain! return false; } else { $referer_host = parse_url($_SERVER['HTTP_REFERER'] . PHP_URL_HOST); + if ($referer_host === false) { + return false; // Malformed URL + } + $refed_host = $referer_host['host'] ?? ""; + $server_host = $_SERVER['HTTP_HOST']; - return ($referer_host === $server_host) ? true : false; + return ($refed_host === $server_host); } } - public static function get_client_ip_server() { + public static function get_client_ip_address() { $ipaddress = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ipaddress = $_SERVER['HTTP_CLIENT_IP']; diff --git a/src/classes/tag_matches.php b/src/classes/tag_matches.php index 5769006..b1ad9ee 100644 --- a/src/classes/tag_matches.php +++ b/src/classes/tag_matches.php @@ -50,11 +50,11 @@ public static function check_tags(string $page): array { if ($total_still_open > 0) { $msg = "{$total_still_open} possibly MISSING closing {$tag_name} !!!"; $alert .= "console.log('{$msg}');\r\n"; - $output .= (\main_tts\configure::get('tts', 'live') === true) ? "\r\n" : "{$ui}{$msg}{$ui_end}\r\n"; + $output .= (\main_tts\is_live()) ? "\r\n" : "{$ui}{$msg}{$ui_end}\r\n"; } elseif ($total_still_open < 0) { $msg = abs($total_still_open) . " possibly MISSING opening {$tag_name} !!!"; $alert .= "console.log('{$msg}');\r\n"; - $output .= (\main_tts\configure::get('tts', 'live') === true) ? "\r\n" : "{$ui}{$msg}{$ui_end}\r\n"; + $output .= (\main_tts\is_live()) ? "\r\n" : "{$ui}{$msg}{$ui_end}\r\n"; } } return array('output' => $output, 'alert' => $alert); diff --git a/src/classes/view.php b/src/classes/view.php index 3a8f9e7..5386f4e 100644 --- a/src/classes/view.php +++ b/src/classes/view.php @@ -22,6 +22,7 @@ namespace tts; final class view { public $white_space_control = false; + public $page_output; private $vars = array(); private $project_dir; private $files = array(); @@ -218,7 +219,7 @@ final class view { $assigns = $this->vars['template_assigns'] ?? []; $filters = $this->vars['template_filters'] ?? null; $registers = $this->vars['template_registers'] ?? []; - $assigns['production'] =(\main_tts\configure::get('tts', 'live') === true) ? true : false; + $assigns['production'] =(\main_tts\is_live()); echo $this->tempalte_engine->render($assigns, $filters, $registers); } } diff --git a/src/main.inc.php b/src/main.inc.php index 3d223eb..af0d475 100644 --- a/src/main.inc.php +++ b/src/main.inc.php @@ -10,8 +10,8 @@ declare(strict_types=1); namespace main_tts; -unset($_REQUEST); // Request is dangerious -unset($_GET); +unset($_REQUEST); // Request is dangerious as order of Vars is not Known +unset($_GET); // Super Globals are not Filtered, so unset them unset($_POST); $mem_baseline = memory_get_usage(); @@ -34,6 +34,31 @@ if (\bs_tts\site_helper::get_testing() === false) { views::ob_start(); } +final class errors { + private static $handle_global_errors = true; + private static $handle_shutdown_errors = true; + private static $handle_exceptions = true; + + public static function set_handle_shutdown_errors(bool $do_handle_errors): void { + self::$handle_shutdown_errors = $do_handle_errors; + } + public static function get_handle_shutdown_errors(): bool { + return self::$handle_shutdown_errors; + } + public static function set_handle_exceptions(bool $do_handle_errors): void { + self::$handle_exceptions = $do_handle_errors; + } + public static function get_handle_exceptions(): bool { + return self::$handle_exceptions; + } + public static function set_handle_global_errors(bool $do_handle_errors): void { + self::$handle_global_errors = $do_handle_errors; + } + public static function get_handle_global_errors(): bool { + return self::$handle_global_errors; + } +} + final class configure { private static $config = []; @@ -191,10 +216,12 @@ final class di { try { $reflection_class = new \ReflectionClass($service_name); } catch (\ReflectionException $e) { - if (! \main_tts\configure::set('tts', 'live')) { + if (! is_live()) { var_dump($e->getTrace()); echo $e->getMessage(); exit; + } else { + throw new \Exception("Failed to resolve resource: {$service_name}!"); } } if (! $reflection_class->isInstantiable()) { @@ -242,7 +269,10 @@ if (extension_loaded('mbstring')) { setlocale(LC_ALL, "en_US.UTF-8"); } -require_once TTS_FRAMEWORK . 'bootstrap/errors.php'; +function is_live() { + return (\bs_tts\common::get_bool(configure::get('tts', 'live'))); +} + require_once TTS_FRAMEWORK . 'bootstrap/common.php'; require_once TTS_FRAMEWORK . 'bootstrap/requires.php'; require_once TTS_FRAMEWORK . 'bootstrap/auto_loader.php'; @@ -253,4 +283,6 @@ registry::get('loader')->add_namespace("bs_tts", TTS_FRAMEWORK . "bootstrap"); registry::get('loader')->add_namespace("tts", TTS_FRAMEWORK . "classes"); registry::get('loader')->add_namespace("tts", TTS_FRAMEWORK . "tests"); +require_once TTS_FRAMEWORK . 'bootstrap/errors.php'; + \bs_tts\site_helper::set_project_namespace(); \ No newline at end of file