Robert 5 months ago
parent 3aa76d8054
commit 2fae0452e9
  1. 27
      Zephir/aes-license/hydraterlicense/hydraterlicense/makelicense.zep
  2. 33
      docs/TODO.md
  3. 142
      src/bootstrap/autoLoader.php
  4. 296
      src/bootstrap/common.php
  5. 121
      src/bootstrap/errors.php
  6. 45
      src/bootstrap/loadAll.php
  7. 297
      src/bootstrap/main.php
  8. 194
      src/bootstrap/requires.php
  9. 628
      src/bootstrap/saferIO.php
  10. 222
      src/bootstrap/siteHelper.php
  11. 354
      src/classes/api.php
  12. 205
      src/classes/app.php
  13. 47
      src/classes/consoleApp.php
  14. 88
      src/classes/enums/safer_io_enums.php
  15. 14
      src/classes/exceptions/Bool_Exception.php
  16. 103
      src/classes/exceptions/DB_Exception.php
  17. 130
      src/classes/html.php
  18. 31
      src/classes/makeLicenseFiles.php
  19. 71
      src/classes/memory_usage.php
  20. 370
      src/classes/misc.php
  21. 69
      src/classes/page_not_found.php
  22. 487
      src/classes/router.php
  23. 264
      src/classes/security.php
  24. 73
      src/classes/traits/database/run_sql.php
  25. 204
      src/classes/traits/database/validation.php
  26. 87
      src/classes/traits/security/csrf_token_functions.php
  27. 149
      src/classes/traits/security/session_hijacking_functions.php
  28. 234
      src/classes/validator.php
  29. 52
      src/views/on_error/404_page.php
  30. 23
      src/views/on_error/dev_error.php
  31. 43
      src/views/on_error/prod_error.php

@ -6,6 +6,33 @@ namespace HydraterLicense;
class MakeLicense class MakeLicense
{ {
public function runSymlink(string directoryPath, string phpFile)->bool {
var target, dir;
boolean isAbsolute;
// Check if the path is a symlink
if !is_link(directoryPath) {
return false; // Not a symlink
}
// Get the symlink target
let target = readlink(directoryPath);
if target === false {
return false; // Failed to read symlink
}
// Check if the target is an absolute path (starts with "/")
let isAbsolute = (strpos(target, "/") === 0);
if !isAbsolute {
// If relative, prepend the directory of the symlink
let dir = dirname(directoryPath);
let target = dir . "/" . target;
}
require realpath(target) . "/" . phpFile;
return true;
}
public function makePassword(int size = 16)->string { public function makePassword(int size = 16)->string {
var r; var r;
let r = this->generateCryptoKey(size); let r = this->generateCryptoKey(size);

@ -1,9 +1,9 @@
# TODOs # TODOs
 [ ] → AutoLoader PSR-4  [x] → AutoLoader PSR-4
[ ] → LoadAll Service and Config files that are ON… [x] → LoadAll Service and Config files that are ON…
[ ] → AEBootLoader and Generator (Encrypted Features and Fall Backs) [x] → AEBootLoader and Generator (Encrypted Features and Fall Backs)
[ ] → Encrypted Sessions [ ] → Encrypted Sessions
@ -17,7 +17,7 @@
[ ] → Routes [ ] → Routes
[ ] → Controllers [x] → Controllers
[ ] → Models [ ] → Models
@ -31,13 +31,13 @@
[ ] → Main Project Tempates [ ] → Main Project Tempates
[ ] → 404 Pages/Dev/Prod Errors [o] → 404 Pages/Dev/Prod Errors
[ ] → CLI Detect [ ] → CLI Detect
[ ] → Paginator [ ] → Paginator
[ ] → Safer I/O [x] → Safer I/O
[ ] → End/Open Tag Matching [ ] → End/Open Tag Matching
@ -51,7 +51,7 @@
[ ] → Logger [ ] → Logger
[ ] → Error Handler [x] → Error Handler
[ ] → CSRF Tokens [ ] → CSRF Tokens
@ -61,7 +61,7 @@
[ ] → Sane Folder Structure and Documentation [ ] → Sane Folder Structure and Documentation
[ ] → Default Routes, then load Controllers [x] → Default Routes, then load Controllers
## Extras: ## Extras:
[ ] → LazyCollections, LazyObjects, Money Class [ ] → LazyCollections, LazyObjects, Money Class
@ -70,19 +70,4 @@
[ ] → RSS Feed [ ] → RSS Feed
[ ] → Private API for Sensitive Transactions: [x] → Private API for Sensitive Transactions:
## API
```
if ($_SERVER['HTTP_REFERER'] != $_SERVER['HTTP_HOST']) {
exit("Form may not be used outside of parent site!");
}
```
## Routes and Controllers
```
$returned_route = \ch\router::execute();
if ($returned_route["found"] === false) {
$app = new \ch\app();
$app->load\controller();
}
```

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace CodeHydrater\bootstrap;
/**
* @author http://php-fig.org/ <info@php-fig.org>
* @copyright Copyright (c) 2013-2017 PHP Framework Interop Group
* @license MIT
* @site https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md
*/
class Psr4AutoloaderClass {
/**
* An associative array where the key is a namespace prefix and the value
* is an array of base directories for classes in that namespace.
*
* @var array
*/
protected $prefixes = [];
protected $loaded_files = [];
/**
* Register loader with SPL autoloader stack.
*
* @return void
*/
public function register() {
spl_autoload_register(array($this, 'load_class'));
}
public function is_loaded(string $prefix): bool {
$prefix = trim($prefix, '\\') . '\\';
return (isset($this->prefixes[$prefix])) ? true : false;
}
public function get_list(): array {
return $this->prefixes;
}
public function get_files_list(): array {
return $this->loaded_files;
}
/**
* Adds a base directory for a namespace prefix.
*
* @param string $prefix The namespace prefix.
* @param string $base_dir A base directory for class files in the
* namespace.
* @param bool $prepend If true, prepend the base directory to the stack
* instead of appending it; this causes it to be searched first rather
* than last.
* @return void
*/
public function add_namespace(string $prefix, string $base_dir, bool $prepend = false): void {
$prefix = trim($prefix, '\\') . '\\';
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
if (isset($this->prefixes[$prefix]) === false) {
$this->prefixes[$prefix] = array();
}
if ($prepend) {
array_unshift($this->prefixes[$prefix], $base_dir);
} else {
array_push($this->prefixes[$prefix], $base_dir);
}
}
/**
* Loads the class file for a given class name.
*
* @param string $class The fully-qualified class name.
* @return mixed The mapped file name on success, or boolean false on
* failure.
*/
public function load_class(string $class): false|string {
if (! strrpos($class, '\\')) {
$ret = ($this->load_mapped_file($class . '\\', $class));
if ($ret !== false) {
return $ret;
}
}
$prefix = $class;
while (false !== $pos = strrpos($prefix, '\\')) {
// retain the trailing namespace separator in the prefix
$prefix = substr($class, 0, $pos + 1);
$relative_class = substr($class, $pos + 1);
$mapped_file = $this->load_mapped_file($prefix, $relative_class);
if ($mapped_file) {
return $mapped_file;
}
// remove the trailing namespace separator for the next iteration
// of strrpos()
$prefix = rtrim($prefix, '\\');
}
// never found a mapped file
return false;
}
/**
* Load the mapped file for a namespace prefix and relative class.
*
* @param string $prefix The namespace prefix.
* @param string $relative_class The relative class name.
* @return mixed Boolean false if no mapped file can be loaded, or the
* name of the mapped file that was loaded.
*/
protected function load_mapped_file(string $prefix, string $relative_class): false | string {
// are there any base directories for this namespace prefix?
if (isset($this->prefixes[$prefix]) === false) {
return false;
}
// look through base directories for this namespace prefix
foreach ($this->prefixes[$prefix] as $base_dir) {
$file = str_replace('\\', '/', $relative_class) . '.php';
if ($this->require_file($base_dir, $file)) {
return $file;
}
}
return false;
}
/**
* If a file exists, require it from the file system.
*
* @param string $file The file to require.
* @return bool True if the file exists, false if not.
*/
private function require_file(string $path, string $file): bool {
$safer_file = requires::safer_file_exists($file, $path);
if ($safer_file !== false) {
if (! isset($this->loaded_files[$safer_file])) {
require $safer_file;
$this->loaded_files[$safer_file] = true;
}
return true;
}
return false;
}
}

@ -0,0 +1,296 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater\bootstrap;
final class common {
protected function __construct() {
}
public static function return_bool_as_int_bit(bool $b_data): int {
return ($b_data) ? 1 : 0; // if true=1, else =0
}
/**
*
* @param type $ret option to check for false error condition
* @return bool true if false/error found
*/
public static function is_error($ret): bool {
$lr = self::string_to_lowercase(trim($ret));
return ($ret === false || $lr === 'false') ? true : false;
}
public static function return_bool_as_yes_no(bool $b_data): string {
return ($b_data) ? 'y' : 'n'; // if true=y, else =n
}
public static function get_bool($bool, $throw = true): bool {
if (is_bool($bool)) {
return $bool;
}
if (is_string($bool)) {
$bool = self::string_to_lowercase(trim($bool));
}
switch ($bool) {
case '0':
case 'false':
case ':false':
case ':null':
case null:
case 'no':
case 'n':
case 'disable':
case 'disabled':
return false;
case '1':
case 'true':
case ':true':
case 'yes':
case 'y':
case 'enable':
case 'enabled':
return true;
default:
if ($throw === true) {
throw new \CodeHydrater\exceptions\Bool_Exception("Value: ({$bool})");
} else {
return false;
}
}
}
public static function get_count($i): int {
return (is_array($i) || is_object($i)) ? count($i) : 0;
}
// Begin Strings Functions here::
public static function string_to_lowercase(string $string, $encoding = null): string {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return (extension_loaded('mbstring')) ? mb_strtolower($string, $encoding) : strtolower($string);
}
public static function string_to_uppercase(string $string, $encoding = null): string {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return (extension_loaded('mbstring')) ? mb_strtoupper($string, $encoding) : strtoupper($string);
}
public static function string_length(string $string, $encoding = null) {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return (extension_loaded('mbstring')) ? mb_strlen($string, $encoding) : strlen($string);
}
public static function string_position(string $string, string $needle, int $offset = 0, $encoding = null) {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return (extension_loaded('mbstring')) ? mb_strpos($string, $needle, $offset, $encoding) : strpos($string, $needle, $offset);
}
public static function string_last_position(string $string, string $needle, int $offset = 0, $encoding = null) {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return (extension_loaded('mbstring')) ? mb_strrpos($string, $needle, $offset, $encoding) : strrpos($string, $needle, $offset);
}
public static function string_trim($string, $charlist = null) {
if (is_null($charlist)) {
return trim($string);
} else {
$charlist = preg_quote($charlist, '/');
return preg_replace("/(^[$charlist]+)|([$charlist]+$)/us", '', $string);
}
}
public static function string_rtrim($string, $charlist = null) {
if (is_null($charlist)) {
return rtrim($string);
} else {
$charlist = preg_quote($charlist, '/');
return preg_replace("/([$charlist]+$)/us", '', $string);
}
}
public static function string_ltrim($string, $charlist = null) {
if (is_null($charlist)) {
return ltrim($string);
} else {
$charlist = preg_quote($charlist, '/');
return preg_replace("/(^[$charlist]+)/us", '', $string);
}
}
public static function string_cmp($str1, $str2, $encoding = null) {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return strcmp(mb_strtoupper($str1, $encoding), mb_strtoupper($str2, $encoding));
}
/*
* Case-Insensitive Find
*/
public static function string_first_position(string $string, string $needle, int $offset = 0, $encoding = null) {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
return (extension_loaded('mbstring')) ? mb_stripos($string, $needle, $offset, $encoding) : stripos($string, $needle, $offset);
}
public static function string_sub_part(string $string, int $offset = 0, int $length = null, $encoding = null) {
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
if ($length === null) {
return (extension_loaded('mbstring')) ? mb_substr($string, $offset, self::string_length($string), $encoding) : substr($string, $offset, strlen($string));
} else {
return (extension_loaded('mbstring')) ? mb_substr($string, $offset, $length, $encoding) : substr($string, $offset, $length);
}
}
/**
* Not really needed as str_contains($string, $needle) now exists!
* Will determine if data was found in string find and returns true if found.
* @param string $data
* @param string $find
* @retval bool
*/
public static function is_string_found(string $data, string $find): bool {
return (self::string_first_position($data, $find) !== false);
}
/**
* Will get only left part of string by length.
* @param string $str
* @param int $length
* @retval type string or false
*/
public static function get_string_left(string $str, int $length): false | string {
return self::string_sub_part($str, 0, $length);
}
public static function is_json(string $maybeJSON): bool {
$version = (float) phpversion();
if ($version >= 8.3) {
return json_validate($maybeJSON);
} else {
$obj = json_decode($maybeJSON);
return (json_last_error() === JSON_ERROR_NONE) ? true : false;
}
}
/**
* Will get only the right part of string by length.
* @param string $str
* @param int $length
* @retval type string or false
*/
public static function get_string_right(string $str, int $length): false | string {
return self::string_sub_part($str, -$length);
}
public static function real_time_output(): void {
header("Content-type: text/plain");
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
while (ob_get_level() > 0) {
// Get the curent level
$level = ob_get_level();
// End the buffering
ob_end_clean();
// If the current level has not changed, abort
if (ob_get_level() == $level) break;
}
}
/**
* Clear out from memory given variable by Reference!
* @param type $sensitive_data
*/
public static function wipe(& $sensitive_data): void {
if (function_exists("sodium_memzero")) {
sodium_memzero($sensitive_data);
}
unset($sensitive_data);
}
/**
* Variable Dump and exit
* Configure of security for show_dumps must be true for debugging.
* @param var - any type will display type and value of contents
* @param bool end - if true ends the script
*/
public static function dump($var = 'nothing', $end = true): void {
if (\main_tts\configure::get('security', 'show_dumps') !== true) {
return;
}
if (!is_object($var)) {
var_dump($var);
echo '<br>';
}
if ($var === false) {
echo 'It is FALSE!';
} elseif ($var === true) {
echo 'It is TRUE!';
} elseif (is_resource($var)) {
echo 'VAR IS a RESOURCE';
} elseif (is_array($var) && self::get_count($var) == 0) {
echo 'VAR IS an EMPTY ARRAY!';
} elseif (is_numeric($var)) {
echo 'VAR is a NUMBER = ' . $var;
} elseif (empty($var) && !is_null($var)) {
echo 'VAR IS EMPTY!';
} elseif ($var == 'nothing') {
echo 'MISSING VAR!';
} elseif (is_null($var)) {
echo 'VAR IS NULL!';
} elseif (is_string($var)) {
echo 'VAR is a STRING = ' . $var;
} else {
echo "<pre style=\"border: 1px solid #000; overflow: auto; margin: 0.5em;\">";
print_r($var);
echo '</pre>';
}
echo '<br><br>';
if ($end === true) {
exit;
}
}
public static function nl2br(string $text): string {
return strtr($text, array("\r\n" => '<br />', "\r" => '<br />', "\n" => '<br />'));
}
}

@ -0,0 +1,121 @@
<?php
namespace CodeHydrater\bootstrap;
// Configuration
if (! defined('BaseDir')) {
define('LOG_FILE', __DIR__ . '/protected/logs/error_log.txt');
} else {
define('LOG_FILE', BaseDir . '/protected/logs/error_log.txt');
}
if (! defined('ENVIRONMENT')) {
define('ENVIRONMENT', 'production'); // 'production' or 'development'
}
/**
* Format message with appropriate colors based on environment
*/
function formatMessage($message, $type = 'error') {
if (PHP_SAPI === 'cli') {
// CLI color formatting
$colors = [
'error' => "\033[31m", // red
'warning' => "\033[33m", // yellow
'notice' => "\033[36m", // cyan
'reset' => "\033[0m" // reset
];
$color = $colors[$type] ?? $colors['error'];
return $color . $message . $colors['reset'] . PHP_EOL;
} else {
// Web HTML formatting
$styles = [
'error' => 'color:red;',
'warning' => 'color:orange;',
'notice' => 'color:blue;'
];
$style = $styles[$type] ?? $styles['error'];
return "<div style='{$style}padding:10px;border:1px solid #f99;margin:10px;'>$message</div>";
}
}
// Custom error handler
set_error_handler(function($errno, $errstr, $errfile, $errline) {
// Skip if error reporting is turned off
if (!(error_reporting() & $errno)) {
return false;
}
$errorTypes = [
E_ERROR => ['ERROR', 'error'],
E_WARNING => ['WARNING', 'warning'],
E_PARSE => ['PARSE ERROR', 'error'],
E_NOTICE => ['NOTICE', 'notice'],
E_CORE_ERROR => ['CORE ERROR', 'error'],
E_CORE_WARNING => ['CORE WARNING', 'warning'],
E_COMPILE_ERROR => ['COMPILE ERROR', 'error'],
E_COMPILE_WARNING => ['COMPILE WARNING', 'warning'],
E_USER_ERROR => ['USER ERROR', 'error'],
E_USER_WARNING => ['USER WARNING', 'warning'],
E_USER_NOTICE => ['USER NOTICE', 'notice'],
E_STRICT => ['STRICT', 'notice'],
E_RECOVERABLE_ERROR=> ['RECOVERABLE ERROR', 'error'],
E_DEPRECATED => ['DEPRECATED', 'warning'],
E_USER_DEPRECATED => ['USER DEPRECATED', 'warning']
];
$errorInfo = $errorTypes[$errno] ?? ['UNKNOWN', 'error'];
$errorType = $errorInfo[0];
$errorCategory = $errorInfo[1];
$logMessage = date('[Y-m-d H:i:s]') . " [$errorType] $errstr in $errfile on line $errline" . PHP_EOL;
$displayMessage = "$errorType: $errstr in $errfile on line $errline";
// Log to file
file_put_contents(LOG_FILE, $logMessage, FILE_APPEND);
// Display in development environment
if (ENVIRONMENT === 'development') {
echo formatMessage($displayMessage, $errorCategory);
}
// Prevent PHP's default error handler
return true;
});
// Handle exceptions
set_exception_handler(function($e) {
$logMessage = date('[Y-m-d H:i:s]') . " [EXCEPTION] " . $e->getMessage() .
" in " . $e->getFile() . " on line " . $e->getLine() . PHP_EOL;
$displayMessage = "UNCAUGHT EXCEPTION: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine();
file_put_contents(LOG_FILE, $logMessage, FILE_APPEND);
if (ENVIRONMENT === 'development') {
echo formatMessage($displayMessage, 'error');
} else {
// In production, show user-friendly message
echo PHP_SAPI === 'cli'
? "An error occurred. Our team has been notified." . PHP_EOL
: "An error occurred. Our team has been notified.";
}
});
// Handle fatal errors
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$logMessage = date('[Y-m-d H:i:s]') . " [FATAL] {$error['message']} in {$error['file']} on line {$error['line']}" . PHP_EOL;
$displayMessage = "FATAL ERROR: {$error['message']} in {$error['file']} on line {$error['line']}";
file_put_contents(LOG_FILE, $logMessage, FILE_APPEND);
if (ENVIRONMENT === 'development') {
echo formatMessage($displayMessage, 'error');
}
}
});
// Test the error handler (uncomment to test)
// trigger_error("This is a test warning", E_USER_WARNING);
// throw new Exception("This is a test exception");
// nonexistentFunction(); // Will trigger a fatal error in shutdown handler

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Bob_586@Yahoo.com>
* @copyright (c) 2025, Robert Strutts
* @license MIT
*/
namespace CodeHydrater\bootstrap;
final class loadAll {
public static function init(string $path): void {
$config_path = $path . "configs";
if (is_dir($config_path)) {
$cdir = scandir($config_path);
if ($cdir !== false) {
self::doLoop($cdir, $config_path);
}
}
$service_path = $path . "services";
if (is_dir($service_path)) {
$sdir = scandir($service_path);
if ($sdir !== false) {
self::doLoop($sdir, $service_path);
}
}
}
private static function doLoop(array $cdir, string $path): void {
foreach($cdir as $key => $file) {
$file = trim($file);
if ($file === '..' || $file === '.') {
continue;
}
$on = substr($file, 0, 3); // grab first three letters
if ($on !== 'on_') {
continue; // It's not ON, so skip it!
}
$file_with_path = $path ."/" . $file;
requires::secure_include($file_with_path, UseDir::FIXED);
}
}
}

@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater\bootstrap;
// Polyfill for PHP 4 to 7
if (!function_exists('str_contains')) {
function str_contains($haystack, $needle) {
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
}
}
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();
require_once CodeHydrater_FRAMEWORK . 'bootstrap/errors.php';
final class views {
public static function ob_start(): void {
if (extension_loaded('mbstring')) {
ob_start('mb_output_handler');
} else {
ob_start();
}
}
}
if (siteHelper::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 = [];
protected function __construct() {
}
public static function exists() { // an Alias to has
return self::has(func_get_args());
}
public static function has(string $name, $key = false): bool {
if ($key === false) {
return (isset(self::$config[strtolower($name)])) ? true : false;
}
return (isset(self::$config[strtolower($name)][strtolower($key)])) ? true : false;
}
public static function get(string $name, $key = false) {
if (isset(self::$config[strtolower($name)])) {
$a = self::$config[strtolower($name)];
if ($key === false) {
return $a;
}
if (isset($a[$key])) {
return $a[$key];
}
}
return null;
}
public static function update() { // an Alias to set
return self::set(func_get_args());
}
public static function set(string $name, $value): void {
self::$config[strtolower($name)] = $value;
}
public static function set_key(string $name, string $key, $value): void {
self::$config[strtolower($name)][strtolower($key)] = $value;
}
public static function add_to_key(string $name, string $key, $value): void {
self::$config[strtolower($name)][strtolower($key)][] = $value;
}
public static function wipe(string $name, $key = false): void {
if (! self::exists($name, $key)) {
return;
}
if ($key === false) {
common::wipe(self::$config[strtolower($name)]);
}
common::wipe(self::$config[strtolower($name)][strtolower($key)]);
}
public static function load_array(array $a): void {
if (isset($a) && is_array($a)) {
foreach ($a as $name => $value) {
self::$config[$name] = $value;
}
}
unset($a);
}
}
final class registry {
private static $registry = [];
protected function __construct() {
}
public static function get(string $name, $key = false) {
if (isset(self::$registry[strtolower($name)])) {
$a = self::$registry[strtolower($name)];
if ($key === false) {
return $a;
}
if (isset($a[$key])) {
return $a[$key];
}
}
return null;
}
public static function set(string $name, $value): bool {
if (array_key_exists(strtolower($name), self::$registry)) {
return false;
}
self::$registry[strtolower($name)] = $value;
return true;
}
}
final class di {
protected $services = [];
public function register(string $service_name, callable $callable): void {
$this->services[$service_name] = $callable;
}
public function has(string $service_name): bool {
return (array_key_exists($service_name, $this->services));
}
public function exists(string $service_name) { // an Alias to has
return $this->has($service_name);
}
/* Note args may be an object or an array maybe more...!
* This will Call/Execute the service
*/
public function get_service(
string $service_name,
$args = [],
...$more
) {
if ($this->has($service_name) ) {
return $this->services[$service_name]($args, $more);
}
return $this->resolve($service_name); // Try to Auto-Wire
}
public function get_auto(string $service_name) {
if ($this->has($service_name) ) {
return $this->services[$service_name]($this);
}
return $this->resolve($service_name); // Try to Auto-Wire
}
public function __set(string $service_name, callable $callable): void {
$this->register($service_name, $callable);
}
public function __get(string $service_name) {
return $this->get_service($service_name);
}
public function list_services_as_array(): array {
return array_keys($this->services);
}
public function list_services_as_string(): string {
return implode(',', array_keys($this->services));
}
public function resolve(string $service_name) {
try {
$reflection_class = new \ReflectionClass($service_name);
} catch (\ReflectionException $e) {
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()) {
throw new \Exception("The Service class: {$service_name} is not instantiable.");
}
$constructor = $reflection_class->getConstructor();
if (! $constructor) {
return new $service_name;
}
$parameters = $constructor->getParameters();
if (! $parameters) {
return new $service_name;
}
$dependencies = array_map(
function(\ReflectionParameter $param) {
$name = $param->getName();
$type = $param->getType();
if (! $type) {
throw new \Exception("Failed to resolve class: {$service_name} becasue param {$name} is missing a type hint.");
}
if ($type instanceof \ReflectionUnionType) {
throw new \Exception("Failed to resolve class: {$service_name} because of union type for param {$name}.");
}
if ($type instanceof \ReflectionNamedType && ! $type->isBuiltin()) {
return $this->get_auto($type->getName());
}
throw new \Exception("Failed to resolve class {$service_name} because of invalid param {$param}.");
}, $parameters
);
return $reflection_class->newInstanceArgs($dependencies);
}
}
// Initialize our Dependency Injector
registry::set('di', new di());
// Setup php for working with Unicode data, if possible
if (extension_loaded('mbstring')) {
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
mb_language('uni');
mb_regex_encoding('UTF-8');
setlocale(LC_ALL, "en_US.UTF-8");
}
function is_live() {
return (common::get_bool(configure::get('CodeHydrater', 'live')));
}
require_once CodeHydrater_FRAMEWORK . 'bootstrap/common.php';
require_once CodeHydrater_FRAMEWORK . 'bootstrap/requires.php';
require_once CodeHydrater_FRAMEWORK . 'bootstrap/autoLoader.php';
registry::set('loader', new Psr4AutoloaderClass);
registry::get('loader')->register();
registry::get('loader')->add_namespace("CodeHydrater\bootstrap", CodeHydrater_FRAMEWORK . "bootstrap");
registry::get('loader')->add_namespace("CodeHydrater", CodeHydrater_FRAMEWORK . "classes");
registry::get('loader')->add_namespace("Project", CodeHydrater_PROJECT);
loadAll::init(CodeHydrater_PROJECT);
$returned_route = \CodeHydrater\router::execute();
if ($returned_route["found"] === false) {
$app = new \CodeHydrater\app();
$app->load_controller();
}

@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater\bootstrap;
use CodeHydrater\bootstrap\common;
enum UseDir: string {
case FIXED = "Fixed";
case FRAMEWORK = "Framework";
case PROJECT = "Project";
case ONERROR = "OnError";
}
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 = common::string_last_position($file, ".");
if ($pos === false) {
$file_type = ".php";
$file_name = $file;
} else {
$file_name = common::get_string_left($file, $pos);
$file_kind = common::get_string_right($file, 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, UseDir $path, $local = null, array $args = array(), bool $load_once = true) {
$dir = match ($path) {
UseDir::FIXED => "",
UseDir::FRAMEWORK => CodeHydrater_FRAMEWORK,
UseDir::ONERROR => CodeHydrater_PROJECT . "views/on_error/",
default => CodeHydrater_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 UseDir $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, UseDir $path = UseDir::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, UseDir $path = UseDir::PROJECT, $local = null, array $args = array(), bool $load_once = true) {
return self::secure_file(true, $file, $path, $local, $args, $load_once);
}
}

@ -0,0 +1,628 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license https://mit-license.org/
*/
/* Sanitize Input, Validate data and Escape output.
* 1) In web development to sanitize means that you remove unsafe
* characters from the input. Makes safer DB inserts/selects, etc...!
* 2) Validation is not sanitization, this step does not remove any bad data,
* validation confirms that the info that is coming to your application meets
* the criteria you want.
* 3) Escape output - to get free from something, or to avoid something. Pay
* attention to not escape the data more than once, you must escape only when
* you received it or when you need to output it. I recommend it on all output only.
*
* Don’t try to sanitize input. Escape output. Perhaps more importantly,
* it gives a false sense of security. What does “unsafe” mean? In what context?
* Sure, <>& are unsafe characters for HTML, but what about CSS, JSON, SQL, or
* even shell scripts? Those have a completely different set of unsafe characters.
*
* Every so often developers talk about “sanitizing user input” to prevent
* cross-site scripting attacks. This is well-intentioned, but leads to a
* false sense of security, and sometimes mangles perfectly good input.
*/
namespace CodeHydrater\bootstrap;
use \CodeHydrater\enums\FIELD_FILTER; // Defined in enum\safer_io_enums
use \CodeHydrater\enums\DB_FILTER;
use \CodeHydrater\enums\HTML_FLAG;
use \CodeHydrater\enums\INPUTS;
/**
* use_io defines public members to be used on safer_io INPUTS
*/
final class use_io {
public $input_var;
public $input_type;
public $field_filter;
public $escape_html;
public $validation_rule;
public $validation_message;
public $skip_the_db;
public $use_db_filter;
}
/**
* use_iol is to Auto-Wire Input Output Logic controllers
* in standard paths defined below
*/
final class use_iol {
public static function auto_wire(
string $root_folder,
string $file,
string $method = 'index',
string $db_service= 'db_mocker'
) {
new \CodeHydrater\enums\safer_io_enums(); // Auto load
registry::set('db', \main_tts\registry::get('di')->get_service($db_service) );
$class_name = "\\Project\\inputs\\{$root_folder}\\{$file}_in";
$input = $class_name::$method();
$class_name = "\\Project\\logic\\{$root_folder}\\{$file}_logic";
$class_name::$method($input);
$class_name = "\\Project\\outputs\\{$root_folder}\\{$file}_out";
return $class_name::$method($input);
}
}
final class saferIO {
private static string $string_of_POST_data = "";
private static array $DATA_INPUTS = [];
protected function __construct() {
}
// Allow anything to set_data_inputs is desired here
public static function set_data_input(string $var_name, mixed $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 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 clear_post_data() {
self::$string_of_POST_data = "";
}
public static function convert_to_utf8(string $in_str): string {
if (! extension_loaded('mbstring')) {
return $in_str;
}
$cur_encoding = mb_detect_encoding($in_str);
if($cur_encoding == "UTF-8" && mb_check_encoding($in_str,"UTF-8")) {
return $in_str;
} else {
return mb_convert_encoding($in_str, 'UTF-8', $cur_encoding);
}
}
// Escape HTML output
public static function h(string $string): string {
$utf8 = self::convert_to_utf8($string);
return htmlspecialchars($utf8, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
// Reverse encode of HTML
public static function html_decode(string $string): string {
return htmlspecialchars_decode($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5);
}
// HTML Purify library
public static function p(string $string): string {
$purifer = registry::get('di')->get_service('html_filter');
if (!$purifer->has_loaded()) {
$purifer->set_defaults();
}
return $purifer->purify($string);
}
// Escape JavaScript output
public static function j($input, int $levels_deep = 512): mixed {
try {
return json_encode($input, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR, $levels_deep);
} catch (\JsonException $ex) {
return $ex;
}
}
public static function json_decode(string $string, bool $return_as_an_array = true, int $levels_deep = 512): mixed {
try {
return json_decode($string, $return_as_an_array, $levels_deep, JSON_THROW_ON_ERROR);
} catch (\JsonException $ex) {
return $ex;
}
}
public static function has_json_error($object): bool {
return ($object instanceof \JsonException);
}
// Escape URL output
public static function u(string $string): string {
return urlencode($string);
}
/*
* Encode HTML kindof... The problem with htmlentities() is that it is not
* very powerful, in fact, it does not escape single quotes, cannot detect
* the character set and does not validate HTML as well.
*/
public static function e(string $string): string {
$utf8 = self::convert_to_utf8($string);
return htmlentities($utf8, ENT_QUOTES, 'UTF-8');
}
public static function de(string $data): string {
return html_entity_decode($data);
}
/*
* Note: Generally, "strip_tags" is just the wrong function.
* Never use it. And if you do, absolutely never use the second parameter,
* because sooner or later someone will abuse it.
*/
public static function get_clean_server_var(string $var): mixed {
return filter_input(INPUT_SERVER, $var, FILTER_UNSAFE_RAW);
}
public static function get_bool($in): bool {
return (filter_var($in, FILTER_VALIDATE_BOOLEAN));
}
/**
* Purpose: To decode JQuery encoded objects, arrays, strings, int, bool types.
* The content must be of application/json.
* Note: It will return null if not valid json. false is not application/json
*/
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 (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 {
switch ($safety_level) {
case HTML_FLAG::raw :
throw new \Exception('Raw HTML not supported!');
case HTML_FLAG::strip :
return strip_tags($input);
case HTML_FLAG::encode :
return self::e($input);
case HTML_FLAG::purify :
return self::p($input);
case HTML_FLAG::escape :
default:
return self::h($input);
}
}
private static function t($item, bool $do_trim = true) {
if ($do_trim) {
if (is_string($item)) {
return trim($item);
}
if (\bs_tts\common::get_count($data)) {
$ret = [];
foreach($data as $text) {
if (is_bool($text) || is_int($text)) {
$ret[] = $text;
continue;
}
if (! is_string($text)) {
continue; // Deny Arrays and Objects here!
}
$ret[] = trim($text);
}
return $ret;
}
}
return $item;
}
private static function find_post_field(string $input_field_name): mixed {
$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 find_get_field(string $input_field_name): mixed {
if (isset($_SERVER['QUERY_STRING'])) {
$query = self::get_clean_server_var('QUERY_STRING');
$get = [];
parse_str($query, $get);
if (isset($get[$input_field_name])) {
return $get[$input_field_name];
}
}
return false;
}
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) {
$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);
}
if (!self::find_get_field("debugging")) {
return null;
}
$get_var = self::find_get_field($input_field_name);
if ($get_var !== false) {
return $get_var;
}
$is_get_set = filter_has_var(INPUT_GET, $input_field_name);
if ($is_get_set) {
return filter_input(INPUT_GET, $input_field_name);
}
return null;
}
if ($input_type === INPUTS::json) {
$rd = self::find_post_field($input_field_name);
if ($rd !== false) {
return $rd;
}
return null;
}
if ($input_type === INPUTS::get) {
$get_var = self::find_get_field($input_field_name);
if ($get_var !== false) {
return $get_var;
}
}
$resolve_input = $input_type->resolve();
$is_set = filter_has_var($resolve_input, $input_field_name);
if ($is_set) {
return filter_input($resolve_input, $input_field_name);
}
return null;
}
/**
*
* @param string $data
* @param array $a['html'] of type HTML_FLAG
* @return string|bool
*/
private static function get_safer_string(string $data, use_io $a): string | bool {
if (isset($a->escape_html) && $a->escape_html instanceof \UnitEnum) {
return self::safer_html($data, $a->escape_html);
}
return self::safer_html($data);
}
private static function get_safer_html($data, use_io $a) {
if (is_string($data)) {
return self::get_safer_string($data, $a);
} else if (common::get_count($data)) {
$ret = [];
foreach($data as $text) {
if (is_bool($text) || is_int($text)) {
$ret[] = $text;
continue;
}
if (! is_string($text)) {
continue; // Deny Arrays and Objects here!
}
$ret[] = self::get_safer_string($text, $a);
}
return $ret;
}
return $data;
}
public static function required_fields_were_NOT_all_submitted(array $data): bool {
$field = $data['name'] ?? false;
$empty = $data['meta'][$field]['empty'] ?? true;
$required = $data['meta'][$field]['validation_rules_set'] ?? false;
return ($empty && $required);
}
private static function sanitize_helper(
string $from,
string $input_field_name,
use_io $a,
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string,
bool $trim = true,
) : array {
$meta = [];
$meta['missing'] = [];
$safer_data = "";
$rules = [];
$messages = [];
if (isset($a->field_filter) && $a->field_filter instanceof \UnitEnum) {
$field_type = $a->field_filter;
} else {
$field_type = $default_filter;
}
if (isset($a->input_var)) {
$user_text = $a->input_var;
} elseif (isset($a->input_type) && $a->input_type instanceof \UnitEnum) {
$user_text = self::get_input_by_type($input_field_name, $a->input_type);
} else {
$ret['name'] = $input_field_name;
$ret['meta']['missing'][] = $input_field_name;
$ret['errors'][$input_field_name] = "Missing Field $input_field_name";
$ret['html'] = null;
$ret['db'] = false;
$ret['logic'] = false;
return $ret;
}
$safer_data = false; // needs to be false to fail the validator
$safer_html_data = null; // should be null for ?? operator to work with it....
if (isset($a->validation_rule)) {
$rules[$input_field_name] = $a->validation_rule;
}
if (isset($a->validation_message) && isset($a->validation_rule)) {
$messages[$input_field_name] = $a->validation_message;
}
$meta[$input_field_name]['validation_rules_set'] = (count($rules)) ? true : false;
$db = (isset($a->skip_the_db)) ? $a->skip_the_db : false;
$meta[$input_field_name]['type'] = $field_type->name;
$meta[$input_field_name]['skip_db'] = $db;
if ($user_text === null) {
$safer_data = null;
$safer_db_data = null;
$safer_html_data = null;
$meta[$input_field_name]['empty'] = true;
} else {
$field_filter_resolved = $field_type->resolve();
$meta[$input_field_name]['empty'] = false;
$safer_data = $user_text;
if ($field_type == FIELD_FILTER::email) {
$safer_data = substr($safer_data, 0, 254);
}
$safer_data = filter_var($safer_data, FILTER_DEFAULT, $field_filter_resolved);
// FallBack: These field types should never allow arrays anyways
if ($field_type == FIELD_FILTER::raw_string ||
$field_type == FIELD_FILTER::raw
) {
if (common::get_count($safer_data)) {
$safer_data = $safer_data[0];
}
}
if ($from === "html") {
$safer_html = self::get_safer_html($safer_data, $a);
if ($safer_html !== false) {
$safer_html_data = $safer_html;
}
if (isset($safer_html_data)) {
$safer_html_data = self::t($safer_html_data, $trim);
}
} else {
$safer_data = self::t($safer_data, $trim);
}
if ($field_type == FIELD_FILTER::integer_number) {
$safer_data = intval($safer_data);
}
if ($field_type == FIELD_FILTER::floating_point) {
$safer_data = floatval($safer_data);
}
if ($from === "db") {
if ($field_type == FIELD_FILTER::integer_number || $field_type == FIELD_FILTER::floating_point) {
$safer_db_data = $safer_data;
} else {
if (isset($a->use_db_filter) && $a->use_db_filter == DB_FILTER::ON) {
$safe_for_db = \tts\extras\safer_sql::get_safer_sql_text($safer_data);
$text = $safe_for_db["text"];
$meta[$input_field_name]['db_filter_status'] = $safe_for_db["status"] ?? \tts\SQL_SAFETY_FLAG::filtered;
} else {
$text = $safer_data;
}
$safer_db_data = $text;
}
}
}
$ret['name'] = $input_field_name;
$ret['meta'] = $meta;
if ($from === "db") {
$ret['db'] = $safer_db_data;
$data[$input_field_name] = $safer_db_data;
} elseif ($from === "logic") {
$ret['logic'] = $safer_data;
$data[$input_field_name] = $safer_data;
} elseif ($from === "html") {
$ret['html'] = $safer_html_data;
$data[$input_field_name] = $safer_html_data;
}
$ret['errors'] = (count($rules)) ? \CodeHydrater\validator::validate($data, $rules, $messages) : [];
return $ret;
}
/**
* As PHP uses the underlying C functions for filesystem related operations,
* it may handle null bytes in a quite unexpected way.
* As null bytes denote the end of a string in C, strings
* containing them won't be considered entirely but rather
* only until a null byte occurs. So, clean it out to
* avoid vulnerable code.
*/
public static function remove_null_byte(string $input): string {
return str_replace(chr(0), '', $input);
}
public static function db_sanitize(
array $inputs,
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string,
bool $trim = true,
) : \Generator {
foreach ($inputs as $input_field_name => $a) {
if (! $a instanceof use_io) {
continue;
}
$yield = static::sanitize_helper(
"db",
$input_field_name,
$a,
$default_filter,
$trim
);
yield $yield;
}
}
public static function logic_sanitize(
array $inputs,
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string,
bool $trim = true,
) : \Generator {
foreach ($inputs as $input_field_name => $a) {
if (! $a instanceof use_io) {
continue;
}
$yield = static::sanitize_helper(
"logic",
$input_field_name,
$a,
$default_filter,
$trim
);
yield $yield;
}
}
/**
* Sanitize the inputs based on the rules an optionally trim the string
* @param FIELD_FILTER $default_filter FILTER_SANITIZE_STRING
* @param bool $trim
* @return Generator
*/
public static function html_escape_and_sanitize(
array $inputs,
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string,
bool $trim = true,
) : \Generator {
foreach ($inputs as $input_field_name => $a) {
if (! $a instanceof use_io) {
continue;
}
$yield = static::sanitize_helper(
"html",
$input_field_name,
$a,
$default_filter,
$trim
);
yield $yield;
}
}
}

@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Bob_586@Yahoo.com>
* @copyright (c) 2025, Robert Strutts
* @license MIT
*/
namespace CodeHydrater\bootstrap;
final class siteHelper {
private static $ROOT;
private static $ROUTE;
private static $PRJ;
private static $FW_DIST;
private static $REQUEST_URI;
private static $REQUEST_METHOD;
private static $USE_SECURE = true;
private static $TESTING;
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_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_route(): string {
return self::$ROUTE;
}
public static function get_root(): ?string {
return self::$ROOT;
}
public static function get_testing() {
return self::$TESTING;
}
public static function get_uri(): string {
return self::$REQUEST_URI;
}
public static function get_method(): string {
return strtoupper(self::$REQUEST_METHOD);
}
public static function get_params() {
return self::$queryParams;
}
public static function get_use_secure(): bool {
return self::$USE_SECURE;
}
/**
* Because $_SERVER['REQUEST_URI'] May only available on Apache,
* we generate an equivalent using other environment variables.
* @return string
*/
public static function request_uri() {
if (self::$REQUEST_URI !== null && !empty(self::$REQUEST_URI)) {
$uri = self::$REQUEST_URI;
} else if (isset($_SERVER['REQUEST_URI'])) {
$uri = saferIO::get_clean_server_var('REQUEST_URI');
} else {
if (isset($_SERVER['argv'])) {
$uri = saferIO::get_clean_server_var('SCRIPT_NAME') . '?' . $_SERVER['argv'][0];
} elseif (isset($_SERVER['QUERY_STRING'])) {
$uri = saferIO::get_clean_server_var('SCRIPT_NAME') . '?' . \bs_tts\safer_io::get_clean_server_var('QUERY_STRING');
} else {
$uri = saferIO::get_clean_server_var('SCRIPT_NAME');
}
}
// Prevent multiple slashes to avoid cross site requests via the Form API.
$uri = '/' . ltrim($uri, '/');
return $uri;
}
public static function get_clean_server_var(string $var): mixed {
return filter_input(INPUT_SERVER, $var, FILTER_UNSAFE_RAW);
}
public static function site_url(): string {
$server_port = self::get_clean_server_var('SERVER_PORT');
$secure_port_on = self::get_clean_server_var('HTTPS');
$use_secure = ($server_port == '443' || $secure_port_on == 'on');
self::$USE_SECURE = $use_secure;
$protocol = ($use_secure) ? 'https://' : 'http://';
define('TTS_PROTOCOL', $protocol);
$domainName = self::get_clean_server_var('HTTP_HOST');
return $protocol . $domainName . "/";
}
public static function resolve($action, ...$params) {
if (is_callable($action)) {
return call_user_func($action, $params);
}
if (!is_array($action)) {
return false;
}
[$class, $method] = $action;
$call_class = "\\" . $class;
if (class_exists($call_class)) {
$auto_class = registry::get('di')->get_auto($call_class);
if (method_exists($call_class, $method)) {
return call_user_func_array([$auto_class, $method], $params);
}
}
return false;
}
private static function set_route(): void {
// 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, '/'));
self::$ROUTE = implode('/', $routes);
}
private static function set_params(): void {
// 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 restrictSite(): void {
if ($_SERVER['HTTP_REFERER'] != $_SERVER['HTTP_HOST']) {
die("Form may not be used outside of parent site!");
}
}
public static function get_cli_args(): string {
$argv = (isset($GLOBALS['argv'])) ? $GLOBALS['argv'] : [];
$args = array_shift($argv); // POP out the SCRIPT_NAME!!
if ($args === null) {
return ""; // NO Args
}
$route = $argv[0] ?? ""; // Keep the Route
$args = array_shift($argv); // POP out the ROUTE!!
if ($args === null) {
return $route;
}
return $route . "?" . ltrim(implode('&', $argv), "&");
}
public static function init(string $ROOT, string $REQUEST_URI, string $REQUEST_METHOD, bool $testing = false) {
self::$ROOT = $ROOT;
self::$REQUEST_URI = $REQUEST_URI;
self::$REQUEST_METHOD = $REQUEST_METHOD;
self::$TESTING = $testing;
self::set_route();
self::set_params();
if (! defined("ASSETS_BASE_REF")) {
define('ASSETS_BASE_REF', "/assets/");
}
define('SITE_URL', self::site_url());
define("BROWSER", self::get_clean_server_var('HTTP_USER_AGENT'));
define("ASSETS_DIR", "/public/assets/");
}
}

@ -0,0 +1,354 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
class api {
const CONTINUE_STATUS = "Continue"; // 100
const SWITCHING_PROTOCOLS = "Switching Protocols"; // 101
const OK = "OK"; // 200
const CREATED = "Created"; // 201
const ACCEPTED = "Accepted"; // 202
const NON_AUTHORITATIVE = "Non-Authoritative Information"; // 203
const NO_CONTENT = "No Content"; // 204
const RESET_CONTENT = "Reset Content"; // 205
const PARTIAL_CONTENT = "Partial Content"; // 206
const ALREADY_REPORTED = "Already Reported"; // 208
const MULTI_STATUS = "Multiple Choices"; // 300
const MOVED_PERMANENTLY = "Moved Permanently"; // 301
const MOVED_TEMPORARILY = "Moved Temporarily"; // 302
const SEE_OTHER = "See Other"; // 303
const NOT_MODIFIED = "Not Modified"; // 304
const USE_PROXY = "Use Proxy"; // 305
const TEMP_REDIRECT = "Temporary Redirect"; // 307
const BAD_REQUEST = "The request cannot be fulfilled due to bad syntax."; // 400
const UNAUTHORIZED = "The authorization details given appear to be invalid."; // 401
const PAYMENT_REQUIRED = "Payment Required"; // 402
const FORBIDDEN = "The requested resource is not accessible."; // 403
const NOT_FOUND = "The requested resource does not exist."; // 404
const METHOD_NOT_ALLOWED = "Method Not Allowed"; // 405
const NOT_ACCEPTABLE = "Not Acceptable"; // 406
const PROXY_AUTH_REQUIRED = "Proxy Authentication Required"; // 407
const REQUEST_TIME_OUT = "Request Time-out"; // 408
const CONFLICT = "Conflict"; // 409
const GONE = "Gone"; // 410
const LENGTH_REQUIRED = "Length Required"; // 411
const PRECONDITION_FAILED = "Precondition Failed"; // 412
const REQUEST_ENTITY_TOO_LARGE = "Request Entity Too Large"; // 413
const REQUEST_URI_TOO_LARGE = "Request-URI Too Large"; // 414
const UNSUPPORTED_FORMAT = "The format requested is not supported by the server."; // 415
const EXPECTATION_FAILED = "Expectation Failed"; // 417
const INTERNAL_ERROR = "An unexpected error occured."; // 500
const NOT_IMPLEMENTED = "Not Implemented"; // 501
const BAD_GATEWAY = "Bad Gateway"; // 502
const MAINTENANCE_MODE = "The requested resource is currently unavailable due to maintenance."; // 503
const GATEWAY_TIME_OUT = "Gateway Time-out"; // 504
const HTTP_VERSION_NOT_SUPPORTED = "HTTP Version not supported"; // 505
/**
* Use Encoder, default JSON
* @param type $data
* @param type $status_code
*/
public static function encode($data, $status_code): void {
$response_type = misc::request_var('return');
switch ($response_type) {
case 'xml':
self::xml_encode($data, $status_code, null);
break;
case 'php':
self::php_encode($data, $status_code);
break;
case 'json':
default:
self::json_encode($data, $status_code);
break;
}
}
/**
* See: HTTP_status_codes.txt
* @link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
* HTTP $code to try, use $default if not valid
*/
public static function get_code_number(int $code, int $default): int {
return ($code > 99 && $code < 600) ? $code : $default;
}
/**
* Encode Error
* @param array $data
*/
public static function error(array $data): void {
$error_code = (isset($data['code'])) ? $data['code'] : 400;
$code = self::get_code_number($error_code, 400);
$data['result'] = false;
if (isset($data['response'])) {
switch ($data['response']) {
case self::CONTINUE_STATUS:
$long_code = "100 Continue";
$data['message'] = self::CONTINUE_STATUS;
break;
case self::SWITCHING_PROTOCOLS:
$long_code = "101 Switching Protocols";
$data['message'] = self::SWITCHING_PROTOCOLS;
break;
case self::MULTI_STATUS:
$long_code = "300 Multiple Choices";
$data['message'] = self::MULTI_STATUS;
break;
case self::MOVED_PERMANENTLY:
$long_code = "301 Moved Permanently";
$data['message'] = self::MOVED_PERMANENTLY;
break;
case self::MOVED_TEMPORARILY:
$long_code = "302 Moved Temporarily";
$data['message'] = self::MOVED_TEMPORARILY;
break;
case self::SEE_OTHER:
$long_code = "303 See Other";
$data['message'] = self::SEE_OTHER;
break;
case self::NOT_MODIFIED:
$long_code = "304 Not Modified";
$data['message'] = self::NOT_MODIFIED;
break;
case self::USE_PROXY:
$long_code = "305 Use Proxy";
$data['message'] = self::USE_PROXY;
break;
case self::TEMP_REDIRECT:
$long_code = "307 Temporary Redirect";
$data['message'] = self::TEMP_REDIRECT;
case self::BAD_REQUEST:
$long_code = "400 Bad Request";
$data['message'] = self::BAD_REQUEST;
break;
case self::UNAUTHORIZED:
$long_code = "401 Unauthorized";
$data['message'] = self::UNAUTHORIZED;
break;
case self::PAYMENT_REQUIRED:
$long_code = "402 Payment Required";
$data['message'] = self::PAYMENT_REQUIRED;
break;
case self::FORBIDDEN:
$long_code = "403 Forbidden";
$data['message'] = self::FORBIDDEN;
break;
case self::NOT_FOUND:
$long_code = "404 Not Found";
$data['message'] = self::NOT_FOUND;
break;
case self::METHOD_NOT_ALLOWED:
$long_code = "405 Method Not Allowed";
$data['message'] = self::METHOD_NOT_ALLOWED;
break;
case self::NOT_ACCEPTABLE:
$long_code = "406 Bad Request";
$data['message'] = self::NOT_ACCEPTABLE;
break;
case self::PROXY_AUTH_REQUIRED:
$long_code = "407 Proxy Authentication Required";
$data['message'] = self::PROXY_AUTH_REQUIRED;
break;
case self::REQUEST_TIME_OUT:
$long_code = "408 Request Time-out";
$data['message'] = self::REQUEST_TIME_OUT;
break;
case self::CONFLICT:
$long_code = "409 Bad Request";
$data['message'] = self::CONFLICT;
break;
case self::GONE:
$long_code = "410 Gone";
$data['message'] = self::GONE;
break;
case self::LENGTH_REQUIRED:
$long_code = "411 Length Required";
$data['message'] = self::LENGTH_REQUIRED;
break;
case self::PRECONDITION_FAILED:
$long_code = "412 Precondition Failed";
$data['message'] = self::PRECONDITION_FAILED;
break;
case self::REQUEST_ENTITY_TOO_LARGE:
$long_code = "413 Request Entity Too Large";
$data['message'] = self::REQUEST_ENTITY_TOO_LARGE;
break;
case self::REQUEST_URI_TOO_LARGE:
$long_code = "414 Request-URI Too Large";
$data['message'] = self::REQUEST_URI_TOO_LARGE;
break;
case self::UNSUPPORTED_FORMAT:
$long_code = "415 Unsupported Media Type";
$data['message'] = self::UNSUPPORTED_FORMAT;
break;
case self::EXPECTATION_FAILED:
$long_code = "417 Expectation Failed";
$data['message'] = self::EXPECTATION_FAILED;
break;
case self::INTERNAL_ERROR:
$long_code = "500 Internal Server Error";
$data['message'] = self::INTERNAL_ERROR;
break;
case self::NOT_IMPLEMENTED:
$long_code = "501 Not Implemented";
$data['message'] = self::NOT_IMPLEMENTED;
break;
case self::BAD_GATEWAY:
$long_code = "502 Bad Gateway";
$data['message'] = self::BAD_GATEWAY;
break;
case self::MAINTENANCE_MODE:
$long_code = "503 Service Unavailable";
$data['message'] = self::MAINTENANCE_MODE;
break;
case self::GATEWAY_TIME_OUT:
$long_code = "504 Gateway Time-out";
$data['message'] = self::GATEWAY_TIME_OUT;
break;
case self::HTTP_VERSION_NOT_SUPPORTED:
$long_code = "505 HTTP Version not supported";
$data['message'] = self::HTTP_VERSION_NOT_SUPPORTED;
break;
default:
$long_code = $code;
break;
}
} else {
$long_code = $code;
}
$data['code'] = $long_code;
$memory_check = bootstrap\common::get_bool(\tts\misc::request_var('debug'));
if ($memory_check) {
$echo = false;
$data['memory_used'] = memory_usage::get_memory_stats($echo);
}
self::encode($data, $long_code);
}
/**
* Encode ok
* @param array $data
*/
public static function ok(array $data = array()): void {
$data['result'] = true;
$code = 200; // OK
$memory_check = bootstrap\common::get_bool(misc::request_var('debug'));
if ($memory_check) {
$echo = false;
$data['memory_used'] = memory_usage::get_memory_stats($echo);
}
if (isset($data['code'])) {
if ($data['code'] > 199 && $data['code'] < 209) {
$code = $data['code'];
}
unset($data['code']);
}
if (isset($data['response'])) {
switch ($data['response']) {
case self::CREATED: $long_code = "201 Created"; break;
case self::ACCEPTED: $long_code = "202 Accepted"; break;
case self::NON_AUTHORITATIVE: $long_code = "203 Non-Authoritative Information"; break;
case self::NO_CONTENT: $long_code = "204 No Content"; break;
case self::RESET_CONTENT: $long_code = "205 Reset Content"; break;
case self::PARTIAL_CONTENT: $long_code = "206 Partial Content"; break;
case self::ALREADY_REPORTED: $long_code = "208 Already Reported"; break;
case self::OK: $long_code = "200 OK"; break;
default: $long_code = $code; break;
}
} else {
$long_code = $code;
}
self::encode($data, $long_code);
}
public static function xml_encode($data, $status_code, $object = null, string $start_tag = 'root') {
if (is_null($object)) {
$xml = new \SimpleXMLElement('<?xml version="1.0"?><'.$start_tag.'/>');
self::xml_encode($status_code, $xml, $data);
if (!headers_sent()) {
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code);
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: *");
header('Content-Type: text/xml; charset=utf-8', true, intval($status_code));
}
echo $xml->asXML();
exit;
} else {
foreach ($data as $key => $value) {
if (is_array($value)) {
$new_object = $object->addChild($key);
self::xml_encode($value, $status_code, $new_object);
} else {
$object->addChild($key, $value);
}
}
}
}
/*
* Purpose to decode XML into an array
*/
public static function xml_decode(string $xmlstring) {
$xml = simplexml_load_string($xmlstring);
$json = json_encode($xml);
return json_decode($json, true);
}
public static function xml_parse(string $htmlStr): string {
$xmlStr = str_replace('<', '&lt;', $htmlStr);
$xmlStr = str_replace('>', '&gt;', $xmlStr);
$xmlStr = str_replace('"', '&quot;', $xmlStr);
$xmlStr = str_replace("'", '&#39;', $xmlStr);
$xmlStr = str_replace("&", '&amp;', $xmlStr);
return $xmlStr;
}
public static function json_encode($data, $status_code): void {
if (!headers_sent()) {
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code);
/*
* Allow JavaScript from anywhere. CORS - Cross Origin Resource Sharing
* @link https://manning-content.s3.amazonaws.com/download/f/54fa960-332e-4a8c-8e7f-1eb213831e5a/CORS_ch01.pdf
*/
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: *");
header('Content-Type: application/json; charset=utf-8', true, intval($status_code));
}
echo json_encode($data);
exit;
}
public static function php_encode($data, $status_code): void {
if (!headers_sent()) {
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code);
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: *");
header('Content-Type: text/php; charset=utf-8', true, intval($status_code));
}
echo serialize($data);
exit;
}
}

@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
use Exception;
/**
*
* @todo Ensure tts JS error reporting works
* @todo Finish Session MGT, Encrypted Sessions
* @todo Make Cached Sessions
* @todo Force Database methods to try Cache First and Save Cache Data
*/
class app {
private $file;
private $class;
private $method;
private $params;
private function get_first_chunks(string $input): string {
$parts = explode('/', $input);
$last = array_pop($parts);
$parts = array(implode('/', $parts), $last);
return $parts[0];
}
private function get_last_part(string $input): string {
$reversedParts = explode('/', strrev($input), 2);
return strrev($reversedParts[0]);
}
/**
* Do not declare a return type here, as it will Error out!!
*/
public function __construct() {
$full_route = bootstrap\siteHelper::get_route();
$no_hmtl = str_replace(".html", "", $full_route);
// Find the Route
$route = $this->get_first_chunks($no_hmtl);
// Find the Method
$the_method = $this->get_last_part($no_hmtl);
$params = bootstrap\siteHelper::get_params();
// Now load Route
$is_from_the_controller = true; // TRUE for from Constructor...
$this->router($route, $the_method, $params, $is_from_the_controller);
}
private function get_ctrl_dir(): string {
$ctrl = (consoleApp::is_cli()) ? "cli_" : "";
return (bootstrap\siteHelper::get_testing()) ? "test_" : $ctrl;
}
/**
* Gets route and checks if valid
* @param string $route
* @param string $method
* @param bool $is_controller
* @retval action
*/
public function router(?string $route, ?string $method, $params, bool $is_controller = false) {
$ROOT = bootstrap\siteHelper::get_root();
$file = "";
$class = "";
if (misc::is_empty($route)) {
$uri = '/app/' . bootstrap\configure::get('CodeHydrater', 'default_project');
} else {
$uri = $route;
}
try {
$filtered_uri = security::filter_uri($uri);
} catch (Exception $ex) {
$this->local404(); // Route Un-Safe URI to Local 404 Page
}
$safe_folders = bootstrap\requires::filter_dir_path(
$this->get_first_chunks($filtered_uri)
);
if (bootstrap\requires::is_dangerous($safe_folders)) {
$this->local404();
}
$safe_folders = rtrim($safe_folders, '/');
if (empty($ROOT)) {
$this->local404();
}
$safe_file = bootstrap\requires::filter_dir_path(
$this->get_last_part($filtered_uri)
);
if (bootstrap\requires::is_dangerous($safe_file)) {
$this->local404();
}
$test = $this->get_ctrl_dir();
$dir = bootstrap\requires::safer_dir_exists($ROOT . "{$test}controllers/" . $safe_folders);
//check for default site controller first
if ($dir === false) {
$this->local404();
} else {
$file = bootstrap\requires::safer_file_exists(basename($safe_file) . '_ctrl.php', $dir);
if ($file !== false) {
$class = security::filter_class($safe_folders) . "\\" . security::filter_class($safe_file . "_ctrl");
} else {
$this->local404();
}
}
if (misc::is_empty($method)) {
$method = ""; // Clear out null if exists
}
if (substr($method, 0, 2) == '__') {
$method = ""; // Stop any magical methods being called
}
if ($is_controller === true) {
$this->file = $file;
$this->class = $class;
$this->method = $method;
$this->params = $params;
} else {
return $this->action($file, $class, $method, $params);
}
}
private function local404() {
page_not_found::error404();
}
/**
* Do controller action
* @param string $file
* @param string $class
* @param string $method
* @retval type
*/
private function action(string $file, string $class, string $method, $params) {
$safer_file = bootstrap\requires::safer_file_exists($file);
if (! $safer_file) {
$this->local404();
}
if (empty($class)) {
$this->local404();
}
$use_api = misc::is_api();
$test = $this->get_ctrl_dir();
$call_class = "\\Project\\" . $test . 'controllers\\' . $class;
$controller = new $call_class();
if ($method === "error" && str_contains($class, "app") &&
method_exists($controller, $method) === false
) {
CodeHydrater_broken_error();
return false;
}
if ($use_api) {
if (empty($method)) {
$method = "index";
}
$method .= "_api";
if (method_exists($controller, $method)) {
return $controller->$method($params);
} else {
page_not_found::error404_cli();
}
} else {
if (!empty($method) && method_exists($controller, $method)) {
return $controller->$method($params);
} else {
if (empty($method) && method_exists($controller, 'index')) {
return $controller->index($params);
} else {
$this->local404();
}
}
}
}
/**
* Does load controller by calling action
*/
public function load_controller() {
return $this->action($this->file, $this->class, $this->method, $this->params);
}
} // end of app

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
final class consoleApp {
public static $is_cli = false;
public static function is_cli() {
if (static::$is_cli) {
return true;
}
if (defined('STDIN')) {
return true;
}
if (php_sapi_name() === 'cli') {
return true;
}
if (array_key_exists('SHELL', $_ENV)) {
return true;
}
// $argv = $_SERVER['argv'] ?? [];
// && count($argv) > 0
if (!isset($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT'])) {
return true;
}
if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
return true;
}
return false;
}
}

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater\enums;
class safer_io_enums {} // Needed to auto-load
enum HTML_FLAG {
case raw; // Dangerious XSS attacks...
case strip;
case encode;
case purify; // Allow safe whitelisted HTML elements/tags
case escape; // safely Escape HTML
}
enum INPUTS: int {
case variable = 998; // User Defined VAR
case debugging = 999; // check POST and then if debugging is set, check GET
case json = 1000; // uses JSON on raw POST BODY
case post = 0; // INPUT_POST;
case get = 1; // INPUT_GET;
case cookie = 2; //INPUT_COOKIE;
case env = 4; // INPUT_ENV;
case server = 5; // INPUT_SERVER;
public function resolve(): int {
return match($this) {
self::post => INPUT_POST,
self::get => INPUT_GET,
self::cookie => INPUT_COOKIE,
self::env => INPUT_ENV,
self::server => INPUT_SERVER,
};
}
}
enum DB_FILTER {
case ON; // Tries to Filter out SQL from User Input
case OFF; // Normal pass thourgh...
}
enum FIELD_FILTER: string {
case raw_string = "string";
case array_of_strings = "strings";
case email = "email-address";
case url = "site-url";
case raw = "unfiltered-non-sanitized";
case integer_number = "integer";
case array_of_ints = "integers";
case floating_point = "float";
case array_of_floats = "floats";
public function resolve() {
return match($this) {
self::raw_string => FILTER_UNSAFE_RAW,
self::array_of_strings => [
'filter' => FILTER_UNSAFE_RAW,
'flags' => FILTER_REQUIRE_ARRAY
],
self::email => FILTER_SANITIZE_EMAIL,
self::url => FILTER_SANITIZE_URL,
self::raw => FILTER_DEFAULT, // Unfiltered, non-sanitized!!!
self::integer_number => [
'filter' => FILTER_SANITIZE_NUMBER_INT,
'flags' => FILTER_REQUIRE_SCALAR
],
self::array_of_ints => [
'filter' => FILTER_SANITIZE_NUMBER_INT,
'flags' => FILTER_REQUIRE_ARRAY
],
self::floating_point => [
'filter' => FILTER_SANITIZE_NUMBER_FLOAT,
'flags' => FILTER_FLAG_ALLOW_FRACTION
],
self::array_of_floats => [
'filter' => FILTER_SANITIZE_NUMBER_FLOAT,
'flags' => FILTER_REQUIRE_ARRAY
],
};
}
}

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace CodeHydrater\exceptions;
class Bool_Exception extends \Exception {
public function errorMessage() {
$errorMsg = $this->getMessage() . ' is not a valid bool type.';
return $errorMsg;
}
}

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace CodeHydrater\exceptions;
enum HANDLE_FAILURE: int {
case static = 0;
case always_throw = 1;
case halt = 2;
case just_return = 3;
}
enum EX_OUTPUT: int {
case static = 0;
case html = 1;
case ajax = 2;
}
enum DEBUGGER: int {
case static = 0;
case basic = 1;
case extended = 2;
}
class DB_Exception extends \Exception {
public static $debug = DEBUGGER::basic;
public static $handle = HANDLE_FAILURE::always_throw;
public static $ouput = EX_OUTPUT::html;
public static $error_message = "Please try again, later...";
/**
* @param \Exception $e
* @param string $message Your Custom Error Message
* @param DEBUGGER $debug (static, basic, extended)
* @param string $do
* (
* "static" = throw on static do_throw is true and no-results,
* "always_throw" = throw always if no-results were found,
* "return" = always return array of results or false and count if none...
* )
*
* @return array can be used with was_empty
* @throws \Exception
*/
public static function customMessage(
\Exception $e,
string $message = "",
DEBUGGER $debug = DEBUGGER::static,
HANDLE_FAILURE $handle = HANDLE_FAILURE::static,
EX_OUTPUT $output = EX_OUTPUT::static
): string {
if (empty($message)) {
$message = self::$error_message;
}
$live = (\main_tts\is_live());
if ($live === false || \tts\session_management::has_user_right('debugger')) {
$msg = ($debug == DEBUGGER::basic ||
(self::$debug === DEBUGGER::basic &&
$debug === DEBUGGER::static)
) ?
$message . PHP_EOL :
$message . " " . $e->getMessage() .PHP_EOL;
} else {
$msg = $message;
}
$ajax = ($output === EX_OUTPUT::ajax ||
(self::$ouput === EX_OUTPUT::static &&
self::$ouput === EX_OUTPUT::ajax)
) ? true : false;
$status_code = 500;
if (!headers_sent() && $ajax) {
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code);
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: *");
header("Content-Type: application/json; charset=UTF-8", true, intval($status_code));
}
$json = [];
$json['had_error'] = '1';
$json['success'] = false;
$json['user_message'] = $msg;
if ($ajax) {
echo json_encode($json);
}
if ($handle == HANDLE_FAILURE::always_throw ||
(self::$handle === HANDLE_FAILURE::always_throw &&
$handle === HANDLE_FAILURE::static)
) {
throw new \Exception($msg);
}
if ($handle === HANDLE_FAILURE::halt ||
(self::$handle === HANDLE_FAILURE::halt &&
$handle === HANDLE_FAILURE::static)) {
die();
}
return self::$msg;
}
}

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
final class html {
/**
* Purpose: To output all HTML select (drop down) option values.
* @param array $options array of key=>value
* @param string $default Item to select on.
* @param string $select_by ['value'] for most of your needs.
* @param array $a as optional options
* @param bool $escape XSS prevention...
* @retval string of option values...
*/
public static function do_options(
array $options,
string $default = '',
string $select_by = 'text',
array $a = array(),
bool $escape = true
): string {
$more = '';
if (count($a)) {
foreach ($a as $k => $v) {
if ($escape) {
$more .= " ". bootstrap\saferIO::h($k) . "=\"" . bootstrap\saferIO::h($v) . "\"";
} else {
$more .= " {$k}=\"{$v}\"";
}
}
}
$values = '';
foreach ($options as $value => $text) {
$compair_to = ($select_by == 'text') ? $text : $value;
$selected = (!empty($default) && $default == $compair_to) ? 'selected' : '';
if ($escape) {
$value = bootstrap\saferIO::h($value);
$text = bootstrap\saferIO::h($text);
}
$values .= "<option value=\"{$value}\" " .
$selected . " " . $more . ">{$text}</option>";
}
return $values;
}
/**
* Used by Memory_Usage script.
* Displays a table from input arrays -- fields
* @param array $header_fields - TH
* @param array $fields - TD
* @param bool $escape XSS prevention...
* @retval void
*/
public static function show_table(
array $header_fields,
array $fields,
bool $escape = true
): void {
$nl = PHP_EOL;
echo "{$nl}<table border='1' cellpadding='1' cellspacing='1'>{$nl}";
echo "\t<tr>{$nl}";
foreach($header_fields as $header_field) {
$field = ($escape) ? \bs_tts\safer_io::h($header_field) : $header_field;
echo "\t\t<th>{$field}</th>{$nl}";
}
echo "\t</tr>{$nl}";
foreach($fields as $field) {
echo "\t<tr>{$nl}";
foreach($field as $td) {
$cell = ($escape) ? \bs_tts\safer_io::h($td) : $td;
echo "\t\t<td>{$cell}</td>{$nl}";
}
echo "\t</tr>{$nl}";
}
echo "</table>{$nl}";
}
/**
* Generators use Memory in an Effient way!!!! So use this.
* @param array $header_fields
* @param array $db_field_names
* @param \Iterator $records
* @param bool $escape
* @return void
*/
public static function show_table_from_generator(
array $header_fields,
\Iterator $records,
bool $escape = true
): void {
$nl = PHP_EOL;
echo "{$nl}<table border='1' cellpadding='1' cellspacing='1'>{$nl}";
echo "\t<tr>{$nl}";
foreach($header_fields as $header_field) {
$field = ($escape) ? \bs_tts\safer_io::h($header_field) : $header_field;
echo "\t\t<th>{$field}</th>{$nl}";
}
echo "\t</tr>{$nl}";
foreach($records as $record) {
echo "\t<tr>{$nl}";
foreach($record as $key => $value) {
if (! is_string($key)) {
continue; // Remove Duplicate records
}
$td = $value ?? "";
if (is_string($td)) {
$cell = ($escape) ? \bs_tts\safer_io::h($td) : $td;
} else {
$cell = (string) $td;
}
echo "\t\t<td>{$cell}</td>{$nl}";
}
echo "\t</tr>{$nl}";
}
echo "</table>{$nl}";
}
}

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Bob_586@Yahoo.com>
* @copyright (c) 2025, Robert Strutts
* @license MIT
*/
namespace CodeHydrater;
use HydraterLicense\MakeLicense;
const PrivatePEM = BaseDir."/keydata/private.pem";
const PublicPEM = BaseDir."/keydata/public.pem";
const AESKeysFile = BaseDir."/src/aeskeys.php";
const LicenseFile = BaseDir."/keydata/license.json";
if (! class_exists("HydraterLicense\MakeLicense")) {
die("Sorry, this extenstion is not availiable!");
}
$license_maker->generateLicense(
Array_For_Files,
ALLOWED_DOMAINS,
PrivatePEM,
PublicPEM,
AESKeysFile,
LicenseFile
);

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
final class memory_usage {
/**
* Used by get_memory_stats to get appropriate units of bytes
* @param type $size in bytes
* @retval string of unit size for bytes
*/
public static function convert_bytes($size) {
$unit=array('b','kb','mb','gb','tb','pb');
return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
}
/**
* Display/Returns Current Memory Usage
* @param bool $echo (default of true) - true: displays, false: returns data
*/
public static function get_memory_stats($echo = true) {
global $mem_baseline;
$check = bootstrap\common::get_bool(misc::request_var('debug'));
if ($check || defined('DEBUG') && DEBUG === true) {
$now_mem = memory_get_usage();
$diff_mem = $now_mem - $mem_baseline;
$s_current = self::convert_bytes($now_mem);
$s_diff = self::convert_bytes($diff_mem);
$s_startup_mem = self::convert_bytes($mem_baseline);
$peak = memory_get_peak_usage();
$s_peak = self::convert_bytes($peak);
$diff_peak = $peak - $mem_baseline;
$s_diff_peak = self::convert_bytes($diff_peak);
if (! $echo) {
$a_fields['start-up'] = $s_startup_mem;
$a_fields['currently'] = $s_current;
$a_fields['total-diff'] = $s_diff;
$a_fields['peak'] = $s_peak;
$a_fields['total-diff-peak'] = $s_diff_peak;
} else {
$a_headers = array('Memory Item', 'Size');
$a_fields[] = array('On-StartUp', $s_startup_mem);
$a_fields[] = array('Currently', $s_current);
$a_fields[] = array('<b>Total Diff</b>', "<b>{$s_diff}</b>");
$a_fields[] = array('&nbsp;', '&nbsp;');
$a_fields[] = array('PEAK', $s_peak);
$a_fields[] = array('<i>Total Diff PEAK</i>', "<i>{$s_diff_peak}</i>");
}
if ($echo === true) {
html::show_table($a_headers, $a_fields, false);
} else {
return $a_fields;
}
}
}
}

@ -0,0 +1,370 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
final class misc {
public static function post_var(string $var, int $filter = FILTER_UNSAFE_RAW): mixed {
return filter_input(INPUT_POST, $var, $filter);
}
public static function get_var(string $var, int $filter = FILTER_UNSAFE_RAW): mixed {
return filter_input(INPUT_GET, $var, $filter);
}
public static function request_var(string $var, int $filter = FILTER_UNSAFE_RAW): mixed {
if (filter_has_var(INPUT_POST, $var)) {
return self::post_var($var, $filter);
}
if (filter_has_var(INPUT_GET, $var)) {
return self::get_var($var, $filter);
}
return "";
}
/**
* Please note that not all web servers support: HTTP_X_REQUESTED_WITH
* So, you need to code more checks!
* @retval boolean true if AJAX request by JQuery, etc...
*/
public static function is_ajax(): bool {
$http_x_requested_with = bootstrap\saferIO::get_clean_server_var('HTTP_X_REQUESTED_WITH');
return (self::compair_it($http_x_requested_with, 'xmlhttprequest'));
}
/**
* Checks if two strings match, case-insensitive
* @param string $var
* @param string $var2
* @retval bool true match
*/
public static function compair_it(?string $var, ?string $var2): bool {
if ($var === null || $var2 === null) {
return false;
}
return (bootstrap\common::string_to_lowercase(trim($var)) === bootstrap\common::string_to_lowercase(trim($var2)));
}
/**
* Multi-Bytes Safe Lowercase and Trim data
* @param string $var
* @retval string
*/
public static function safe_lowercase_trim(string $var): string {
return (bootstrap\common::string_to_lowercase(trim($var)));
}
/**
* Is valid ID > zero
* @param int $var
* @retval bool is valid ID
*/
public static function is_valid_id(int $var): bool {
return ($var > 0) ? true : false;
}
/**
* Is NOT a valid ID... (ID < 1)
* @param int $var
* @retval bool true if not valid ID
*/
public static function is_not_valid_id(int $var): bool {
return ($var < 1) ? true : false;
}
/**
* Check if string is not empty
* @param string $var
* @retval bool
*/
public static function is_not_empty(?string $var): bool {
return ($var !== null && !empty(trim($var)));
}
/**
* Check if string is empty
* @param string $var
* @retval bool
*/
public static function is_empty(?string $var): bool {
return ($var === null || empty(trim($var)) );
}
/**
* RESTFUL method detection for API
*/
public static function get_request_method(): array {
$method = filter_input(INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_ENCODED);
switch (strtoupper($method)) {
case 'GET': // only retrieve data
$crud = 'read';
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true];
case 'HEAD': // Has no response body
$crud = 'read';
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true];
case 'POST':
$crud = 'create';
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true];
case 'PUT':
$crud = 'replace';
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true];
case 'PATCH':
$crud = 'modify';
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true];
case 'DELETE':
$crud = 'delete';
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true];
case 'CONNECT':
case 'COPY':
case 'OPTIONS':
case 'TRACE':
case 'LINK':
case 'UNLINK':
case 'PURGE':
case 'LOCK':
case 'UNLOCK':
case 'PROPFIND':
case 'VIEW':
return ['method' => $method, 'crud' => false, 'restful' => false, 'valid' => true];
default:
return ['method' => $method, 'crud' => false, 'resetful' => false, 'valid' => false];
}
}
/**
* Fetch File
* @param string $var
* @retval content or :null
*/
public static function file_var(string $var) {
return (isset($_FILES[$var])) ? $_FILES[$var] : ':null';
}
/**
* How to prevent CRLF injection (Http response HEADER splitting) in php
* https://stackoverflow.com/questions/31318151/how-to-prevent-crlf-injection-http-response-splitting-in-php
* https://medium.com/@tomnomnom/crlf-injection-into-phps-curl-options-e2e0d7cfe545
*/
public static function abort_on_crlf(string $data): string {
// if (self::$_safe_replacement_for_crlf) {
$data = str_ireplace(["%0d", "%0a"], "", $data);
// }
$cr = "/%0D|%0d/";
$lf = "/%0A|%0a/";
$cr_check = preg_match($cr, $data);
$lf_check = preg_match($lf, $data);
if ($cr_check > 0 || $lf_check > 0) {
throw new \Exception('Attempted: CRLF header hack detected. Aborting.');
}
return $data;
}
public static function filter_two_decimal_number($number): string {
return number_format(filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION), 2);
}
/**
* Filter/Sanitize Email data
* @param string $data
* @retval string of sanitized email data or :false
*/
public static function clean_email(string $data): string {
$email = filter_var(trim($data), FILTER_SANITIZE_EMAIL);
return (filter_var($email, FILTER_VALIDATE_EMAIL)) ? $email : ':false';
}
/**
* Encode and Filter/Sanitize URL data
* @param string $data
* @retval string of sanitized url data
*/
public static function encode_url_var(string $data): string {
return urlencode(filter_var(trim($data), FILTER_SANITIZE_URL));
}
public static function is_url(string $url): bool {
return filter_var($url, FILTER_VALIDATE_URL);
}
/**
* Decode URL data
* @param string $data
* @retval string of url data
*/
public static function decode_url_var(string $data): string {
return trim(urldecode($data));
}
public static function is_regex($exp): bool {
return (filter_var($exp, FILTER_VALIDATE_REGEXP));
}
public static function email_hyper_link(string $address, string $subject, string $body): string {
$mail_to_uri = 'mailto:' . rawurlencode($address) . '?subject=' . rawurlencode($subject) . '&body=' . rawurlencode($body);
return htmlspecialchars($mail_to_uri);
}
// GET requests should not make changes
// Only POST requests should make changes
public static function request_is_get() {
return $_SERVER['REQUEST_METHOD'] === 'GET';
}
public static function request_is_post() {
return $_SERVER['REQUEST_METHOD'] === 'POST';
}
/**
* Helper function for safe_url -> does URL encode
* @param string $q
* @retval string
*/
private static function _parm_encode(string $q): string {
if (substr_count($q, "=") > 0) {
$parms = explode("=", $q);
$parm = self::encode_url_var($parms[0]);
$value = self::encode_url_var($parms[1]);
return $parm . "=" . $value . "&";
} else {
return self::encode_url_var($q);
}
}
/**
* Encode and make safe URL
* @param string $vars
* @retval string the safe url
*/
public static function safe_url(string $vars): string {
$new = '';
if (substr_count($vars, "&") > 0) {
$qs = explode("&", $vars);
foreach ($qs as $q) {
$new .= self::_parm_encode($q);
}
$new = rtrim($new, "&");
} else {
$new = rtrim(self::_parm_encode($vars), "&");
}
return $new;
}
/**
* Purpose to auto create url based on if short url is needed
* @param string $project - Which folder
* @param string $route
* @param string $method
* @param type $vars
* @retval string url
*/
public static function get_url(string $route, string $method, $vars = ''): string {
$route = ltrim($route, "/");
if (is_array($vars)) {
$vars = http_build_query($vars);
} elseif (is_string($vars) && !empty($vars)) {
$vars = self::safe_url($vars);
}
if (bootstrap\configure::get('CodeHydrater', 'short_url') === true) {
$vars = (!empty($vars)) ? "?{$vars}" : '';
return PROJECT_BASE_REF . "/{$route}/{$method}.html{$vars}";
} else {
$vars = (!empty($vars)) ? "&{$vars}" : '';
return PROJECT_BASE_REF . "?route={$route}&m={$method}{$vars}";
}
}
/**
* Purpose to auto create API url based on if short url is needed
* @param string $route
* @param string $method
* @param type $vars
* @retval string API url
*/
public static function get_api_url(string $route, string $method, $vars = ''): string {
$route = ltrim($route, "/");
if (is_array($vars)) {
$vars = http_build_query($vars);
} elseif (is_string($vars) && !empty($vars)) {
$vars = self::safe_url($vars);
}
if (bootstrap\configure::get("CodeHydrater", 'short_url') === true) {
$vars = (!empty($vars)) ? "?{$vars}" : '?x=0';
return PROJECT_BASE_REF . "/api/{$route}/{$method}{$vars}&api=true";
} else {
$vars = (!empty($vars)) ? "&{$vars}" : '';
return PROJECT_BASE_REF . "?route={$route}&m={$method}&code=/api/{$vars}&api=true";
}
}
/**
* Checks if current URI is from an API call
* @retval bool
*/
public static function is_api(): bool {
$uri = bootstrap\siteHelper::request_uri();
if (strlen($uri) > 2) {
return ((substr_count($uri, "/api/") == 1 && self::get_var('api') == 'true') || (substr_count($uri, "/api2/") == 1 && self::get_var('api2') == 'true')) ? true : false;
} else {
return false;
}
}
/**
* site http://php.net/manual/en/function.base64-encode.php
*/
public static function base64url_encode(string $data): string {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
public static function base64url_decode(string $data): string {
//return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
return base64_decode( strtr( $data, '-_', '+/') . str_repeat('=', 3 - ( 3 + strlen( $data )) % 4 ));
}
public static function get_globals(array $skip = ['route', 'm'], array $only_these = []): string {
$the_request = '';
$getArray = filter_input_array(INPUT_GET) ?? [];
$postArray = filter_input_array(INPUT_POST) ?? [];
$my_requests = array_merge($getArray, $postArray);
foreach ($my_requests as $key => $value) {
if (count($skip) && in_array($key, $skip)) {
continue;
}
$safe_key = filter_var(trim($key), FILTER_UNSAFE_RAW);
$url_key = self::encode_url_var($safe_key);
$data = filter_var($key);
$safe_value = filter_var(trim($data), FILTER_UNSAFE_RAW);
$url_value = self::encode_url_var($safe_value);
if (in_array($key, $only_these) || !count($only_these)) {
$the_request .= '&' . $url_key . '=' . $url_value;
}
}
return strip_tags($the_request);
}
}

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
class page_not_found {
/**
* Command Line Route - Invalid Error
*/
public static function error404_cli(): void {
$err = "CLI 404 Page not found! Invalid Route/Method." . PHP_EOL;
$argv = (isset($GLOBALS['argv'])) ? $GLOBALS['argv'] : array();
$num_args = count($argv);
if ( $num_args > 1 ) {
$err .= $argv[1];
}
$exists = bootstrap\registry::get('di')->exists('log');
if ($exists) {
$log = bootstrap\registry::get('di')->get_service('log', ['CLI_404s']);
$log->write($err);
}
echo $err;
exit(1);
}
/**
* Displays 404 Page not Found
*/
public static function error404(): void {
if (consoleApp::is_cli()) {
self::error404_cli();
} else {
$use_api = misc::is_api();
}
if ($use_api === true) {
self::api_method_not_found();
}
// Show 404, Page Not Found Error Page!
if (bootstrap\requires::secure_include('404_page.php', bootstrap\UseDir::ONERROR) === false) {
$loaded = bootstrap\requires::secure_include('views/on_error/404_page.php', bootstrap\UseDir::FRAMEWORK);
if ($loaded === false) {
echo "<h1>404 Page Not Found!</h1>";
}
}
exit(1);
}
/**
* API Method was not found do API 400 Error
*/
private static function api_method_not_found(): void {
$status = 400; // Bad Request
$bad_request = api::BAD_REQUEST;
api::error(array('response' => $bad_request, 'code' => $status, 'reason' => 'Command not found'));
}
}

@ -0,0 +1,487 @@
<?php
declare(strict_types=1);
/**
* @license MIT
* @copyright (c) 2018, Patrik Mokrý
* @author Patrik Mokrý
* @link https://github.com/MokryPatrik/PHP-Router
*/
namespace CodeHydrater;
class router
{
private static $instance = null;
public static $URL = null;
public static $REQUEST = null;
public static $params = [];
private static $queryParams = [];
private static $routes = []; // All routes
public static $route = ""; // Return current route
private static $last = null; // Last route added
private static $prefix = null;
private static $name = null;
private static $doNotIncludeInParams = [];
private static $shortcuts = [
'i' => '(\d+)', // Any Number
's' => '(\w+)', // Any Word
'locale' => '(sk|en)->en'
];
/**
* Init new self instance
*/
public static function init() {
self::$instance = new self();
}
/**
* Assign name to route
*
* @param $name
*/
public function name($name)
{
$route = self::$routes[self::$last];
unset(self::$routes[self::$last]);
self::$routes[self::$name . $name] = $route;
}
/**
* Add custom shortcut
* shortcut with default value -> $regex = (val1|val2)->val1;
*
* @param $shortcut
* @param $regex
*/
public static function addShortcut($shortcut, $regex)
{
self::$shortcuts[$shortcut] = $regex;
}
/**
* Get link from route name and params
*
* @param $route
* @param array $params
* @param $absolute
* @return null
*/
public static function link($route, $params = [], $absolute = true)
{
if (!isset(self::$routes[$route])) return null;
$route = self::$routes[$route];
$link = "";
foreach ($route['params'] as $key => $param) {
if (isset($param['real'])) {
$link .= $param['pattern'] . '/';
} else if (isset($params[$param['name']])) {
// Chcek if param has default
if (isset($param['default']) && $param['default'] !== $params[$param['name']]) {
$link .= $params[$param['name']] . '/';
}
}
}
// Add absolute path
if ($absolute) {
$link = self::$URL . $link;
}
// Cut slash at the end
return rtrim($link, '/');
}
/**
* Redirect to specific route or defined location
*
* @param $route
* @param array $params
*/
public static function redirect($route, $params = [])
{
if (isset(self::$routes[$route])) {
$route = self::link($route, $params);
}
header('Location: ' . $route, true);
die();
}
/**
* Create prefixed routes
*
* @param $prefix
* @param $callback
* @param string $name
* @param array $doNotIncludeInParams
*/
public static function prefix($prefix, $callback, $name = '', $doNotIncludeInParams = [])
{
self::$prefix = $prefix;
self::$name = $name;
self::$doNotIncludeInParams = $doNotIncludeInParams;
call_user_func($callback);
self::$prefix = null;
self::$name = null;
self::$doNotIncludeInParams = [];
}
/**
* Create route
*
* @param $route
* @param $action
* @param array $method
* @return Router
*/
public static function route($route, $action, $method = ["POST", "GET"])
{
$prefix = self::$prefix;
if (empty($route) && !empty(self::$prefix)) {
$prefix = rtrim(self::$prefix, '/');
}
$explodedRoute = explode("/", $prefix . $route);
$params = [];
$pattern = "";
// create route
foreach ($explodedRoute as $key => $r) {
if (strpos($r, '}?') !== false) {
$r = self::dynamic($r, $params, true);
$pattern = substr($pattern, 0, -1);
$dyn = true;
} else if (strpos($r, '}') !== false) {
$r = self::dynamic($r, $params, false);
$pattern = substr($pattern, 0, -1);
$dyn = true;
} else {
$params[] = [
'pattern' => $r,
'real' => false
];
}
$pattern .= ($key == 0 && !isset($dyn) ? '/' : '') . $r . '/';
}
// Create pattern
$pattern = substr($pattern, 0, -1) . '/?';
$pattern = '~^' . str_replace('/', '\/', $pattern) . '$~';
// Save data to static property
$name = uniqid();
self::$routes[$name] = [
'route' => $route,
'pattern' => $pattern,
'params' => $params,
'action' => $action,
'method' => $method,
'doNotIncludeInParams' => self::$doNotIncludeInParams
];
// Set last added
self::$last = $name;
// return self instance
if (self::$instance == null) {
self::init();
}
return self::$instance;
}
public static function get($route, $action) {
return self::route($route, $action, ['GET']);
}
public static function post($route, $action) {
return self::route($route, $action, ['POST']);
}
public static function put($route, $action) {
return self::route($route, $action, ['PUT']);
}
public static function delete($route, $action) {
return self::route($route, $action, ['DELETE']);
}
public static function any($route, $action) {
return self::route($route, $action, ['GET', 'POST', 'PUT', 'DELETE']);
}
/**
* Create all routes for resource (CRUD)
*
* @param $route
* @param $controller
* @param string $name
* @param string $shortcut
*/
public static function resource($route, $controller, $name = null, $shortcut = 's')
{
self::$name = self::$name . $name;
self::get($route . '/{page::paginator}?', $controller . "@index")->name('index');
self::get($route . "/create", $controller . "@create")->name('create');
self::post($route, $controller . "@store")->name('store');
self::get($route . "/{id::" . $shortcut . "}", $controller . "@show")->name('show');
self::get($route . "/{id::" . $shortcut . "}/edit", $controller . "@edit")->name('edit');
self::put($route . "/{id::" . $shortcut . "}", $controller . "@update")->name('update');
self::delete($route . "/{id::" . $shortcut . "}", $controller . "@destroy")->name('destroy');
self::get($route . "/{id::" . $shortcut . "}/delete", $controller . "@destroy")->name('destroy_');
self::$name = str_replace($name, '', self::$name);
}
/**
* Handle dynamic parameter in route
*
* @param string $route
* @param array $params
* @param boolean $optional
* @return mixed
*/
private static function dynamic($route, &$params, $optional = false)
{
$shortcut = self::rules($route);
$name = str_replace('{', '', $route);
if (!$optional) {
$name = str_replace('}', '', $name);
} else {
$name = str_replace('}?', '', $name);
}
if (strpos($name, '::')) {
$name = substr($name, 0, strpos($name, "::"));
}
if (array_search($name, array_column($params, 'name')) === false) {
$pattern = str_replace('(', '(/', $shortcut['shortcut']);
$pattern = str_replace('|', '|/', $pattern);
// If is optional add ? at the end of pattern
if ($optional) {
$pattern .= '?';
}
$params[] = [
'name' => $name,
'pattern' => $pattern,
'default' => $shortcut['default']
];
} else {
throw new \Exception('Parameter with name ' . $name . ' has been already defined');
}
return $pattern;
}
/**
* Dynamic route rules
*
* @param $route
* @return mixed
*/
private static function rules($route)
{
if (preg_match('~::(.*?)}~', $route, $match)) {
list(, $shortcut) = $match;
if (isset(self::$shortcuts[$shortcut])) {
// Try to get default value from shortcut
$default = false;
$shortcut = self::$shortcuts[$shortcut];
if (preg_match('~->(.*?)$~', $shortcut, $match)) {
$default = $match[1];
$shortcut = str_replace($match[0], '', $shortcut);
}
// Return shortcut and default value
return [
'shortcut' => $shortcut,
'default' => $default
];
}
}
return [
'shortcut' => self::$shortcuts['s'],
'default' => false
];
}
/**
* get_all_routes -> Added by Robert Strutts to auto load routes
* Namespace must start with Project
* Needs a routes folder, then
* either an routes.php or test_routes.php files must exists
* with a method called get!
*/
private static function get_all_routes(bool $testing = false) {
$route_name = (consoleApp::is_cli()) ? "cli_routes" : "routes";
$routes = ($testing) ? "test_routes" : $route_name;
$routes_class = "\\Project\\routes\\{$routes}";
if (class_exists($routes_class)) {
if (method_exists($routes_class, "get")) {
$callback = "{$routes_class}::get";
call_user_func($callback);
}
}
}
/**
* Execute router
* @todo add Kernel/Requests...
*/
public static function execute() {
$ROOT = bootstrap\siteHelper::get_root();
$request_uri = bootstrap\siteHelper::get_uri();
$request_method = bootstrap\siteHelper::get_method();
$testing = bootstrap\siteHelper::get_testing();
// Generate request and absolute path
self::generateURL($ROOT, $request_uri);
// Fecth all Routes from the Project
self::get_all_routes($testing);
// Get query string
$request = explode('?', $request_uri);
$queryParams = [];
if (isset($request[1])) {
$queryParams = $request[1];
parse_str($queryParams, $queryParams);
self::$queryParams = $queryParams;
}
// Modify request
$request = '/' . trim(self::$REQUEST, '/');
// Find route
foreach (self::$routes as $routeKey => $route) {
$post_method = \tts\misc::post_var("_method");
$matchMethod = in_array($request_method, $route['method']) || ($post_method !== null
&& in_array($post_method, $route['method']));
if (preg_match($route['pattern'], $request, $match) && $matchMethod) {
// Default variables
$explodedRequest = explode('/', ltrim($request, '/'));
$routeParams = $route['params'];
// Match request params with params in array - static params
foreach ($explodedRequest as $key => $value) {
foreach ($routeParams as $k => $routeParam) {
// Go to the next request part
if (isset($routeParam['real']) && $routeParam['pattern'] == $value) {
unset($routeParams[$k]);
unset($explodedRequest[$key]);
break;
}
}
}
$number = "(/\d+)";
$params = [];
// Match request params with params in array - dynamic params
foreach ($routeParams as $k => $routeParam) {
$value = $explodedRequest[$k] ?? false;
$default = $routeParam['default'] ?? true;
$pattern = $routeParam['pattern'] ?? "";
$wild = str_contains($pattern, "?");
if (! $wild && ($value === false && $default)) {
$params[$routeParam['name']] = null;
} else if ($wild && ($value === false && ! $default)) {
// Let the default function params be used by not setting anything here!
} else if (preg_match('~' . $pattern . '~', '/' . $value, $match)) {
if (str_contains($pattern, $number)) {
$params[$routeParam['name']] = (int) $value;
} else {
$params[$routeParam['name']] = $value;
}
unset($routeParams[$k]);
}
}
// Merge with query params
self::$params = array_merge($params, $queryParams);
// Setup default route and url
self::$route = $route;
foreach ($params as $key => $value) {
if (in_array($key, $route['doNotIncludeInParams']) && !is_numeric($key)) {
unset($params[$key]);
}
}
// Call action
if (is_callable($route['action'])) {
$returned = call_user_func_array($route['action'], $params);
return ["found"=> true, "returned"=> $returned];
} else if (strpos($route['action'], '@') !== false) {
// call controller
list($controller, $method) = explode('@', $route['action']);
// init new controller
$controller = new $controller;
// Check if class has parent
$parentControllers = class_parents($controller);
if (!empty($parentControllers)) {
end($parentControllers);
$parentController = $parentControllers[key($parentControllers)];
$parentController = new $parentController;
// Add properties to parent class
foreach ($params as $key => $value) {
$parentController::$params[$key] = $value;
}
}
//Call method
if (method_exists($controller, $method)) {
$returned = call_user_func_array([$controller, $method], $params);
return ["found"=> true, "returned"=> $returned];
}
}
}
}
return ["found"=>false];
}
/**
* Generate URL
*
* @param $ROOT
*/
private static function generateURL(string $ROOT, string $request_uri)
{
$https = bootstrap\saferIO::get_clean_server_var('HTTPS');
$baseLink = ($https === 'on') ? "https" : "http";
$server_name = bootstrap\saferIO::get_clean_server_var('SERVER_NAME');
$baseLink .= "://" . $server_name;
$port = bootstrap\saferIO::get_clean_server_var('SERVER_PORT');
$baseLink .= ($port !== '80') ? ':' . $port : ':';
$baseRequest = '';
$request = $request_uri;
foreach (explode('/', $ROOT) as $key => $value) {
if ($value == '') continue;
if (preg_match('~/' . $value . '~', $request)) {
$baseRequest .= $value . '/';
}
$request = preg_replace('~/' . $value . '~', '', $request, 1);
}
self::$URL = $baseLink . '/' . $baseRequest;
self::$REQUEST = explode('?', $request)[0];
}
}

@ -0,0 +1,264 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace CodeHydrater;
final class security {
use traits\security\csrf_token_functions;
use traits\security\session_hijacking_functions;
/**
* Get unique IDs for database
* @return int
*/
public static function get_unique_number(): int {
return abs(crc32(microtime()));
}
/**
* Get token
* @return string
*/
public static function get_unique_id(): string {
$more_entropy = true;
$prefix = ""; // Blank is a rand string
return md5(uniqid($prefix, $more_entropy));
}
public static function use_hmac(string $algo, string $pepper) {
if (!function_exists("hash_hmac_algos")) {
throw new \Exception("hash_hmac not installed!");
}
if (strpos($algo, "md") !== false) {
throw new \Exception("MD is too weak!");
}
if (strpos($algo, "sha1") !== false) {
throw new \Exception("sha1 is too weak!");
}
$allowed = hash_hmac_algos();
if (in_array(strtolower($algo), $allowed)) {
return hash_hmac($algo, $pepper);
}
throw new \Exception("hmac algo not found!");
}
/*
* Consider MD5 and SHA1 which are fast and efficient,
* making them ideal for check summing and file verification.
* However, their speed makes them unsuitable for hashing a
* user’s password. With today’s computational power of
* modern CPUs/GPUs and cloud computing, a hashed password
* can be cracked by brute force in a matter of minutes.
* It’s fairly trivial to quickly generate billions of MD5
* hashes from random words until a match is found, thereby
* revealing the original plain-text password. Instead,
* intentionally slower hashing algorithms such as bcrypt
* or Argon2 should be used.
*/
public static function find_default_hash_algo() {
if (defined("PASSWORD_ARGON2ID"))
return PASSWORD_ARGON2ID;
if (defined("PASSWORD_ARGON2"))
return PASSWORD_ARGON2;
if (defined("PASSWORD_DEFAULT"))
return PASSWORD_DEFAULT;
if (defined("PASSWORD_BCRYPT"))
return PASSWORD_BCRYPT;
return false;
}
/*
* The password_hash() function not only uses a secure
* one-way hashing algorithm, but it automatically handles
* salt and prevents time based side-channel attacks.
*/
public static function do_password_hash(string $password): bool | string {
$pwd_peppered = self::make_hash($password);
$hash_algo = \main_tts\configure::get(
"security",
"hash_algo"
);
if (! empty($hash_algo)) {
return password_hash($pwd_peppered, $hash_algo);
}
return password_hash($pwd_peppered, self::find_default_hash_algo());
}
public static function do_password_verify(
string $input_pwd, $db_password
): bool {
$pwd_peppered = self::make_hash($input_pwd);
return password_verify($pwd_peppered, $db_password);
}
/**
* Make a secure Hash
* @param string $text to encode
* @param string $level (weak, low, high, max)
* @return string new Hashed
*/
public static function make_hash(string $text): string {
$level = bootstrap\configure::get('security', 'hash_level');
if (empty($level)) {
$level = "normal";
}
$pepper = bootstrap\configure::get('security', 'pepper_pwd');
if (strlen($pepper) < 12) {
throw new \Exception("Pepper Password, too short!");
}
$salt = bootstrap\configure::get('security', 'salt_pwd');
if (strlen($salt) < 5) {
throw new \Exception("Salt Password, too short!");
}
switch (strtolower($level)) {
case 'max':
// Prefer computing using HMAC
if (function_exists("hash_hmac")) {
return hash_hmac("sha512", $text, $pepper);
}
// Sha512 hash is the next best thing
if (function_exists("hash")) {
return hash("sha512", $salt . $text . $pepper);
}
case 'normal':
// Prefer computing using HMAC
if (function_exists("hash_hmac")) {
return hash_hmac("sha256", $text, $pepper);
}
// Sha256 hash is the next best thing
if (function_exists("hash")) {
return hash("sha256", $salt . $text . $pepper);
}
case 'weak':
throw \Exception("Too weak of a Hash FN");
// return sha1($salt . $text . $pepper);
case 'low':
throw \Exception("Too weak of a Hash FN");
// return md5($salt . md5($text . $pepper));
default:
break;
}
return self::use_hmac($level, $pepper);
}
/**
* @method filter_class
* @param type $class
* Please NEVER add a period or SLASH as it will allow BAD things!
* IT should be a-zA-Z0-9_ and that's it.
* @retval string of safe class name
*/
public static function filter_class(string $class): string {
if (bootstrap\requires::is_dangerous($class)) {
throw new \Exception("Dangerious URI!");
}
return preg_replace('/[^a-zA-Z0-9_]/', '', $class);
}
/**
* Filter possible unsafe URI, prevent ../up-level hackers
* @param string $uri
* @return string Safe URI
*/
public static function filter_uri(string $uri): string {
if (bootstrap\requires::is_dangerous($uri) === true) {
throw new \Exception("Dangerious URI!");
}
return bootstrap\requires::filter_file_name($uri);
}
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));
}
/**
* Is the server on the local test domain name
* @return bool SERVER Domain name is on whitelist
*/
public static function is_server_name_on_domain_list(array $whitelist): bool {
if (!isset($_SERVER['SERVER_NAME'])) {
return false;
}
return (in_array($_SERVER['SERVER_NAME'], $whitelist));
}
/**
* Check if same Domain as Server
* @return bool
*/
public static function request_is_same_domain(): bool {
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 ($refed_host === $server_host);
}
}
public static function get_client_ip_address() {
$ipaddress = '';
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
} else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else if (isset($_SERVER['HTTP_X_FORWARDED'])) {
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
} else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
} else if (isset($_SERVER['HTTP_FORWARDED'])) {
$ipaddress = $_SERVER['HTTP_FORWARDED'];
} else if (isset($_SERVER['REMOTE_ADDR'])) {
$ipaddress = $_SERVER['REMOTE_ADDR'];
} else {
$ipaddress = 'UNKNOWN';
}
return $ipaddress;
}
/**
* Make sure uploads (LIKE Images, etc...) do NOT run PHP code!!
* Checks for PHP tags inside of file.
* @param string $file
* @return bool true if PHP was found
*/
public static function file_contains_php(string $file): bool {
$file_handle = fopen($file, "r");
while (!feof($file_handle)) {
$line = fgets($file_handle);
$pos = strpos($line, '<?php');
if ($pos !== false) {
fclose($file_handle);
return true;
}
}
fclose($file_handle);
return false;
}
}

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace tts\traits\database;
trait run_sql {
public function run($sql, $bind=""): int {
$pdostmt = $this->pdo->prepare(trim($sql));
if (\bs_tts\common::get_count($bind) > 0) {
$exec = $pdostmt->execute($bind);
} else {
$exec = $pdostmt->execute();
}
return $pdostmt->rowCount();
}
public function run_select($sql) {
$pdostmt = $this->pdo->prepare(trim($sql));
$exec = $pdostmt->execute();
return $pdostmt->fetchAll(\PDO::FETCH_ASSOC);
}
private function describe_table(string $table) {
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
$tick_table = (strrpos($table, "`") === false) ? "`{$table}`" : $table;
if ($driver == 'sqlite') {
$sql = "PRAGMA table_info({$tick_table});";
$key = "name";
} elseif ($driver == 'mysql') {
$sql = "DESCRIBE {$tick_table};";
$key = "Field";
} elseif ($driver == 'pgsql') {
$sql = "SELECT column_name FROM information_schema.columns WHERE table_name = '{$table}';";
$key = "column_name";
} else {
return false;
}
return ['sql'=>$sql, 'key'=>$key];
}
public function get_fields(string $table) {
$describe = $this->describe_table($table);
if (false !== ($list = $this->run_select($describe['sql']))) {
$fields = array();
if (count($list) == 0) {
return array();
}
foreach ($list as $record) {
$fields[] = $record[$describe['key']];
}
return $fields;
}
return array();
}
private function filter(string $table, array $info): array {
$describe = $this->describe_table($table);
if ($describe === false) {
return array_keys($info);
}
if (false !== ($list = $this->run_select($describe['sql']))) {
$fields = array();
foreach ($list as $record) {
$fields[] = $record[$describe['key']];
}
return array_values(array_intersect($fields, array_keys($info)));
}
return array();
}
}

@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace tts\traits\database;
trait validation {
/**
* Validate current class members
* @retval bool true valid, false failed tests
*/
public function validate_mysql(): bool {
$tbl = (\bs_tts\common::is_string_found($this->table, "`")) ? $this->table : "`{$this->table}`";
foreach ($this->members as $field => $value) {
if ($field == $this->primary_key) {
continue;
}
$validation_field = $this->get_vaildation_member($field);
if (isset($validation_field['native_type']) && isset($validation_field['len'])) {
$type = strtoupper($validation_field['native_type']);
$len = intval($validation_field['len']);
$meta = $validation_field;
} else {
$meta = $this->get_MySQL_meta_field($field, $this->table);
$type = (isset($meta['native_type']) ? $meta['native_type'] : '');
$len = $meta['len'];
}
switch ($type) { //This should be all uppercase input.
case 'SHORT': //Small INT
case 'INT24': //MED INT
case 'LONGLONG': //BIG INT or SERIAL is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.
case 'LONG': // Integers
if (!preg_match('/^[0-9]*$/', $value)) {
$this->do_verr("Failed Validation: NOT a digit {$type} {$field}");
return false;
} // Does not allow decimal numbers!!
if (strlen($value) > $len) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
break;
case 'FLOAT':
if (strlen($value) > $len) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
if (!is_float($value)) {
$this->do_verr("Failed Validation: NOT a float {$type} {$field}");
return false;
}
break;
case 'NEWDECIMAL':
$prec = intval($meta['precision']);
$maxlen = $len - $prec;
if (!\bs_tts\common::is_string_found($value, '.')) {
$this->do_verr("Failed Validation: No Decimal Found in field {$field}");
return false;
}
$x = explode('.', $value);
if (strlen($x[0]) >= ($maxlen - 1) || strlen($x[1]) > $prec) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
break;
case 'DOUBLE':
if (strlen($value) > $len) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
if (!is_double($value)) {
$this->do_verr("Failed Validation: NOT a double {$type} {$field}");
return false;
}
break;
case 'BLOB': // Text
if ($len == '4294967295' || $len == '16777215')
continue 2; //Too Big to process, 16777215 MEDIUMTEXT
if (strlen($value) > $len) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
break;
case 'VAR_STRING': // VARCHAR or VARBINARY
case 'STRING': //CHAR or BINARY
if (strlen($value) > $len) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
break;
case 'DATE':
if (!$this->is_valid_mysql_date($value)) {
$this->do_verr("Failed Validation: invalid date in {$field}");
return false;
}
break;
case 'TIME':
if (!$this->is_valid_mysql_time($value)) {
$this->do_verr("Failed Validation: invalid time in {$field}");
return false;
}
break;
case 'TIMESTAMP':
case 'DATETIME':
if (strlen($value) > $len) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
if (!$this->is_valid_mysql_timestamp($value)) {
$this->do_verr("Failed Validation: invalid timestamp in {$field}");
return false;
}
break;
default: //TINYINT, Bit, Bool, or Year is the default for no meta data
//if (!is_Digits($value)) return false; //This fails so its commented out.
if ($len == 3) { // Tiny INT
if (intval($value) > 255) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
if (intval($value) < -127) {
$this->do_verr("Failed Validation: too short {$type} {$field}");
return false;
}
} elseif ($len == 1) { // Bit or Bool
if (intval($value) > 9) {
$this->do_verr("Failed Validation: too long {$type} {$field}");
return false;
}
if (intval($value) < 0) {
$this->do_verr("Failed Validation: too short {$type} {$field}");
return false;
}
}
break;
}
}
return true;
}
public function get_MySQL_meta_field(string $field, string $table): array {
$query = "SELECT `{$field}` FROM {$table} LIMIT 1";
$pdo_stmt = $this->pdo->prepare($query);
$pdo_stmt->execute();
return $pdo_stmt->getColumnMeta(0);
}
/**
* Check if valid timestamp/datetime
* @param type $Str
* @return type
*/
public function is_valid_mysql_timestamp(string $Str): bool {
$Stamp = strtotime($Str);
$Month = date('m', $Stamp);
$Day = date('d', $Stamp);
$Year = date('Y', $Stamp);
return checkdate($Month, $Day, $Year);
}
public function is_valid_mysql_date(string $str): bool {
$date_parts = explode('-', $str);
if (\main_tts\common::count($date_parts) != 3)
return false;
if ((strlen($date_parts[0]) != 4) || (!is_numeric($date_parts[0])))
return false;
if ((strlen($date_parts[1]) != 2) || (!is_numeric($date_parts[1])))
return false;
if ((strlen($date_parts[2]) != 2) || (!is_numeric($date_parts[2])))
return false;
if (!checkdate($date_parts[1], $date_parts[2], $date_parts[0]))
return false;
return true;
}
public function is_valid_mysql_time(string $str): bool {
return (strtotime($str) === false) ? false : true;
}
/**
* Helper function for validate mysql
* Will echo if not live error message
* If enabled will also log the error.
* @param string $msg error message
*/
private function do_verr(string $msg): void {
$this->error_message = $msg;
$exists = \main_tts\registry::get('di')->exists('log');
if ($exists && \main_tts\configure::get('database', 'log_validation_errors') === true) {
$log = \main_tts\registry::get('di')->get_service('log', ['validation_errors']);
$log->write($msg);
}
}
}

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace CodeHydrater\traits\security;
trait csrf_token_functions {
/**
* Get an Cross-Site Request Forge - Prevention Token
* @return string
*/
public static function csrf_token(): string {
return self::get_unique_id();
}
/**
* Set Session to use CSRF Token
* @return string CSRF Token
*/
public static function create_csrf_token(): string {
$token = self::csrf_token();
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_time'] = time();
return $token;
}
/**
* Destroy CSRF Token from Session
* @return bool success
*/
public static function destroy_csrf_token(): bool {
$_SESSION['csrf_token'] = null;
$_SESSION['csrf_token_time'] = null;
return true;
}
/**
* Get CSRF Token for use with HTML Form
* @return string Hidden Form with token set
*/
public static function csrf_token_tag(): string {
$token = self::create_csrf_token();
return "<input type=\"hidden\" name=\"csrf_token\" value=\"" . $token . "\">";
}
/**
* Check if POST data CSRF Token is Valid
* @return bool is valid
*/
public static function csrf_token_is_valid(): bool {
$is_csrf = filter_has_var(INPUT_POST, 'csrf_token');
if ($is_csrf) {
$user_token = \tts\misc::post_var('csrf_token');
$stored_token = $_SESSION['csrf_token'] ?? '';
if (empty($stored_token)) {
return false;
}
return \tts\misc::compair_it($user_token, $stored_token);
} else {
return false;
}
}
/**
* Optional check to see if token is also recent
* @return bool
*/
public static function csrf_token_is_recent(): bool {
$max_elapsed = intval(\main_tts\configure::get(
'security',
'max_token_age'
));
if ($max_elapsed < 30) {
$max_elapsed = 60 * 60 * 24; // 1 day
}
if (isset($_SESSION['csrf_token_time'])) {
$stored_time = $_SESSION['csrf_token_time'];
return ($stored_time + $max_elapsed) >= time();
} else {
// Remove expired token
self::destroy_csrf_token();
return false;
}
}
}

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace CodeHydrater\traits\security;
/**
* author site did not work to contact him July 30th, 2023
* @link https://gist.github.com/ohidxy/cc361bb40fb3fbb3fe9112c99d51f69e
* Codes to prevent SESSION Hijacking and Fixation in PHP
*/
trait session_hijacking_functions {
public static function init() {
$my_sess = \CodeHydrater\main\configure::get("site", "session_name");
session_name($my_sess);
session_start();
}
// Function to forcibly end the session
public static function end_session() {
// Use both for compatibility with all browsers
// and all versions of PHP.
session_unset();
session_destroy();
}
// Does the request IP match the stored value?
public static function request_ip_matches_session() {
// return false if either value is not set
if (!isset($_SESSION['ip']) || !isset($_SERVER['REMOTE_ADDR'])) {
return false;
}
if ($_SESSION['ip'] === $_SERVER['REMOTE_ADDR']) {
return true;
} else {
return false;
}
}
// Does the request user agent match the stored value?
public static function request_user_agent_matches_session() {
// return false if either value is not set
if (!isset($_SESSION['user_agent']) || !isset($_SERVER['HTTP_USER_AGENT'])) {
return false;
}
if ($_SESSION['user_agent'] === $_SERVER['HTTP_USER_AGENT']) {
return true;
} else {
return false;
}
}
// Has too much time passed since the last login?
public static function last_login_is_recent() {
$max_elapsed = intval(\CodeHydrater\main\configure::get(
'security',
'max_last_login_age'
));
if ($max_elapsed < 30) {
$max_elapsed = 60 * 60 * 24; // 1 day
}
// return false if value is not set
if (!isset($_SESSION['last_login'])) {
return false;
}
if (($_SESSION['last_login'] + $max_elapsed) >= time()) {
return true;
} else {
return false;
}
}
// Should the session be considered valid?
public static function is_session_valid() {
$check_ip = true;
$check_user_agent = true;
$check_last_login = true;
if ($check_ip && !self::request_ip_matches_session()) {
return false;
}
if ($check_user_agent && !self::request_user_agent_matches_session()) {
return false;
}
if ($check_last_login && !self::last_login_is_recent()) {
return false;
}
return true;
}
// If session is not valid, end and redirect to login page.
public static function confirm_session_is_valid() {
if (!self::is_session_valid()) {
self::end_session();
// Note that header redirection requires output buffering
// to be turned on or requires nothing has been output
// (not even whitespace).
header("Location: login.php");
exit;
}
}
// Is user logged in already?
public static function is_logged_in() {
return (isset($_SESSION['logged_in']) && $_SESSION['logged_in']);
}
// If user is not logged in, end and redirect to login page.
public static function confirm_user_logged_in() {
if (!self::is_logged_in()) {
self::end_session();
// Note that header redirection requires output buffering
// to be turned on or requires nothing has been output
// (not even whitespace).
header("Location: login.php");
exit;
}
}
// Actions to preform after every successful login
public static function after_successful_login() {
// Regenerate session ID to invalidate the old one.
// Super important to prevent session hijacking/fixation.
session_regenerate_id();
$_SESSION['logged_in'] = true;
// Save these values in the session, even when checks aren't enabled
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['last_login'] = time();
}
// Actions to preform after every successful logout
public static function after_successful_logout() {
$_SESSION['logged_in'] = false;
self::end_session();
}
// Actions to preform before giving access to any
// access-restricted page.
public static function before_every_protected_page() {
self::confirm_user_logged_in();
self::confirm_session_is_valid();
}
}

@ -0,0 +1,234 @@
<?php
declare(strict_types=1);
/**
* Modified from phptutorial.net
* @link https://www.phptutorial.net/php-tutorial/php-validation/
*/
namespace CodeHydrater;
class validator {
const DEFAULT_VALIDATION_ERRORS = [
'required' => 'Please enter the %s',
'email' => 'The %s is not a valid email address',
'less_than' => 'The %s number must be less than %d',
'greater_than' => 'The %s number must be greater than %d',
'number_range' => 'The %s number must be in range of %d to %d',
'min' => 'The %s must have at least %s characters',
'max' => 'The %s must have at most %s characters',
'between' => 'The %s must have between %d and %d characters',
'same' => 'The %s must match with %s',
'alphanumeric' => 'The %s should have only letters and numbers',
'secure' => 'The %s must have between 8 and 64 characters and contain at least one number, one upper case letter, one lower case letter and one special character',
'valid_email_domain' => 'The %s email address is not active',
'valid_domain' => 'The %s domain name is not active',
];
private static function make_arrays(array $data, $field): array {
$dataset = [];
if (isset($data[$field])) {
if (bootstrap\common::get_count($data[$field])) {
foreach($data[$field] as $the_data) {
$dataset[] = $the_data;
}
} else {
$dataset[] = $data[$field];
}
} else {
$dataset[] = null; // If field is null, force null set
}
return $dataset;
}
public static function validate(
array $data,
array $fields,
array $messages = []
): array {
// Split the array by a separator, trim each element
// and return the array
$split = fn($str, $separator) => array_map('trim', explode($separator, $str));
// get the message rules
$rule_messages = array_filter($messages, fn($message) => is_string($message));
// overwrite the default message
$validation_errors = array_merge(self::DEFAULT_VALIDATION_ERRORS, $rule_messages);
$errors = [];
foreach ($fields as $field => $option) {
foreach(self::make_arrays($data, $field) as $index=>$v) {
$data[$field] = $v; // Force update on arrays
$rules = $split($option, '|');
foreach ($rules as $rule) {
// get rule name params
$params = [];
// if the rule has parameters e.g., min: 1
if (strpos($rule, ':')) {
[$rule_name, $param_str] = $split($rule, ':');
$params = $split($param_str, ',');
} else {
$rule_name = trim($rule);
}
// by convention, the callback should be is_<rule> e.g.,is_required
$fn = 'is_' . $rule_name;
$callable = self::class . "::{$fn}";
if (is_callable($callable)) {
$pass = $callable($data, $field, ...$params);
if (!$pass) {
// get the error message for a specific field and rule if exists
// otherwise get the error message from the $validation_errors
$errors[$field] = sprintf(
$messages[$field][$rule_name] ?? $validation_errors[$rule_name],
$field,
...$params
);
}
}
}
}
}
return $errors;
}
private static function is_required(array $data, string $field): bool {
if (isset($data[$field])) {
if (bootstrap\common::get_count($data[$field])) {
return false; // Should not be an array here
}
if (is_string($data[$field])) {
return (trim($data[$field]) !== '');
}
if (is_int($data[$field])) {
return true;
}
}
return false;
}
private static function is_email(array $data, string $field): bool {
if (empty($data[$field])) {
return true;
}
return filter_var($data[$field], FILTER_VALIDATE_EMAIL);
}
private static function is_min(array $data, string $field, string $min): bool {
if (!isset($data[$field])) {
return true;
}
return mb_strlen($data[$field]) >= intval($min);
}
private static function is_max(array $data, string $field, string $max): bool {
if (!isset($data[$field])) {
return true;
}
return mb_strlen($data[$field]) <= intval($max);
}
private static function is_greater_than(array $data, string $field, string $min): bool {
if (!isset($data[$field])) {
return true;
}
return intval($data[$field]) > intval($min);
}
private static function is_less_than(array $data, string $field, string $max): bool {
if (!isset($data[$field])) {
return true;
}
return intval($data[$field]) < intval($max);
}
private static function is_number_range(array $data, string $field, string $min, string $max): bool {
if (!isset($data[$field])) {
return true;
}
$no = intval($data[$field]);
return $no >= intval($min) && $no <= intval($max);
}
private static function is_between(array $data, string $field, string $min, string $max): bool {
if (!isset($data[$field])) {
return true;
}
$len = mb_strlen($data[$field]);
return $len >= intval($min) && $len <= intval($max);
}
private static function is_same(array $data, string $field, string $other): bool {
if (isset($data[$field]) && isset($data[$other])) {
return $data[$field] === $data[$other];
}
return false;
}
private static function is_alphanumeric(array $data, string $field): bool {
if (!isset($data[$field])) {
return true;
}
return ctype_alnum($data[$field]);
}
private static function is_secure(array $data, string $field): bool {
if (!isset($data[$field])) {
return false;
}
// Is 8 to 64 CHRs
$pattern = "#.*^(?=.{8,64})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*\W).*$#";
return preg_match($pattern, $data[$field]);
}
private static function is_valid_email_domain(array $data, string $field): bool {
if (!isset($data[$field])) {
return false;
}
$domain = ltrim(stristr($data[$field], '@'), '@') . '.';
return checkdnsrr($domain, 'MX');
}
private static function is_valid_domain(array $data, string $field): bool {
if (!isset($data[$field])) {
return false;
}
return checkdnsrr($data[$field], 'A')
|| checkdnsrr($data[$field], 'AAAA')
|| checkdnsrr($data[$field], 'CNAME');
}
}
/*
$data = ['email'=>'jim@aol.com'];
$fields = ['email' => 'required | email'];
$errors = \CodeHydrater\validator::validate($data, $fields);
print_r($errors);
*/
/*
* 'firstname' => 'required | max:255',
'lastname' => 'required | max: 255',
'address' => 'required | min: 10 | max:255',
'zipcode' => 'between: 5,6',
'username' => 'required | alphanumeric | between: 3,255',
'email' => 'required | email | valid_email_domain',
*/

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
$protocol = "HTTP/1.0";
if ( "HTTP/1.1" == $_SERVER["SERVER_PROTOCOL"] ) {
$protocol = "HTTP/1.1";
}
header( "{$protocol} 404 Not Found", true, 404 );
header('Content-type: text/html; charset=utf-8');
?>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="language" content="english">
<meta name="robots" content="no-follow">
<link rel="shortcut icon" href="/assets/favicon/favicon.ico">
<title>404 Page not found!</title>
<style>
@media only screen and (max-width: 600px) {
#nopage {
height: 150px;
width: 300px;
}
}
@media only screen and (min-width: 600px) {
#nopage {
height: 500px;
width: 1500px;
}
}
</style>
</head>
<body>
<div id="wrap">
<img src="/assets/images/404page.jpg" alt="Page not found." id="nopage"/>
<header><h1>404 Page not found!<h1></header>
<h3>Our apologies for the temporary inconvenience.</h3>
</div>
</body>
</html>
<?php
exit;

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="">
<meta name="description" content="">
<meta name="author" content="Robert Strutts">
<meta name="language" content="english">
<meta name="robots" content="">
<meta name="copyright" content="2014-<?= date('Y'); ?>">
<title>DEV ERROR!</title>
<link rel="shortcut icon" href="/assets/favicon/favicon.ico">
</head>
<body id="my-page">
<div id="wrap">
<div id="autosavemessage"></div>
<?= $local->page_output; ?>
</div> <!-- end wrap -->
</body>
</html>

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
define('PRODUCTION', 600);
define('MAINTENACE', 3600); // 1 hour = 3600 seconds
define('RETRY_AFTER', PRODUCTION);
if(! headers_sent()) {
header('HTTP/1.1 503 Service Temporarily Unavailable');
header('Status: 503 Service Temporarily Unavailable');
header('Retry-After: ' . RETRY_AFTER);
}
?>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="Robert Strutts">
<meta name="language" content="english">
<meta name="robots" content="NOINDEX, NOFOLLOW">
<meta name="copyright" content="2014-<?php echo date('Y'); ?>">
<link rel="shortcut icon" href="/assets/favicon/favicon.ico">
<title>Sorry, we had an error...</title>
<style>
body { padding: 20px; background: #C00; color: white; font-size: 40px; }
</style>
</head>
<body>
<h1>Sorry, we had an error...</h1>
<p>We apologize for any inconvenience this may cause.<p>
</body>
</html>
<?php exit;
Loading…
Cancel
Save