diff --git a/documents/folders.txt b/documents/folders.txt
index cdfcc98..4f8904d 100644
--- a/documents/folders.txt
+++ b/documents/folders.txt
@@ -63,10 +63,11 @@ tts_framework/src
│ │ │ ├── crypto.php (Newer sodium_crypto)
│ │ │ ├── password_storage.php (Hash and Verify Hashed passwords)
│ │ │ └── sodium_storage.php (Secure Cookies, Sessions, etc...)
-│ │ ├── sessions (@todo Fix session destroy to erase old sessions)
+│ │ ├── sessions
+│ │ │ ├── cookie_sessions.php
│ │ │ ├── file_sessions.php
│ │ │ └── redis_sessions.php
-│ │ ├── sessions.php (@todo Fix security to encrypt/decrypt sessions)
+│ │ ├── sessions.php
│ │ ├── simple_rest.php (demo REST API helper)
│ │ └── twilio.php (Loads Twilio Vendor files into Name Space)
│ ├── session_management.php (Starts PHP secure Sessions)
@@ -83,12 +84,9 @@ tts_framework/src
│ ├── validator.php (validates HTML Forms)
│ └── view.php (Loads view files from common folders you defined)
├── main.inc.php (Bootstraps App, sets configure, registry, di, and name-spaces)
-├── templates
-│ └── dev_error.php (When NOT Live, show Exceptions/Errors)
└── views
- ├── 404.php (Default 404 Page Not Found Page/Image)
- ├── default
- │ └── broken.php (Debug Trace)
- └── errors.php (when Live, this: Sorry, we had an error... Page is used)
+ ├── 404_page.php (Default 404 Page Not Found Page/Image)
+ ├── dev_error.php (When NOT Live, show Exceptions/Errors)
+ └── prod_errors.php (when Live, this: Sorry, we had an error... Page is used)
-~72 files
+~73 files
diff --git a/src/bootstrap/errors.php b/src/bootstrap/errors.php
index 35d50ef..390b78d 100644
--- a/src/bootstrap/errors.php
+++ b/src/bootstrap/errors.php
@@ -33,20 +33,18 @@ function tts_broken_error($ex = ''): void {
}
if (\main_tts\is_live()) {
- $resource = \main_tts\configure::get('tts', 'error_page');
- $exists = \bs_tts\requires::secure_include('views/errors', $resource); // Show Broken Page
+ if (\bs_tts\requires::secure_include('prod_error.php', 'on_error') === false) {
+ $exists = \bs_tts\requires::secure_include('views/on_error/prod_error.php', 'tts'); // Show Broken Page
+ }
if ($exists === false) {
echo "
Sorry, we had an error...
We apologize for any inconvenience this may cause.
";
}
} else {
- $view = new \tts\view();
- $view->set_view('broken', 'tts');
- $view->set('ex', $ex);
- $view->fetch([]);
+ echo "Error Page
Please go back to another page...
";
}
}
-function tts_mini_view(string $file, string $render, string $msg): string {
+function tts_mini_view(string $file, string $msg): string {
@ob_end_clean(); // Wipe all HTML content before this....
if (!headers_sent()) {
header('Content-Type: text/html; charset=utf-8', true, 200);
@@ -58,8 +56,9 @@ function tts_mini_view(string $file, string $render, string $msg): string {
$mini = new stdClass();
$mini->page_output = $msg;
- $file = 'templates/' . $file;
- \bs_tts\requires::secure_include($file, $render, $mini);
+ if (\bs_tts\requires::secure_include($file, 'on_error', $mini) === false) {
+ \bs_tts\requires::secure_include("views/on_error/" . $file, 'tts', $mini);
+ }
// If you really must close all of your output buffers except one, this'll do it:
while (ob_get_level() > $saved_ob_level) {
@@ -273,7 +272,7 @@ function tts_exception_handler(\Throwable $exception) {
if (\main_tts\is_live()) {
tts_global_error_handler(E_USER_ERROR, $err);
} else {
- echo tts_mini_view('dev_error', 'tts', $msg);
+ echo tts_mini_view('dev_error', $msg);
}
exit(1);
}
@@ -319,8 +318,8 @@ function tts_custom_error_checker(): void {
$mini .= '' . PHP_EOL;
$mini .= "{$a_errors['message']}, in file: {$a_errors['file']}, on line #{$a_errors['line']}.";
$mini .= '
' . PHP_EOL;
- //$mini .= '';
- echo tts_mini_view('dev_error', 'tts', $mini);
+
+ echo tts_mini_view('dev_error', $mini);
exit(1);
}
}
@@ -368,15 +367,19 @@ function tts_global_error_handler(int $errno = 0, string $errstr = '', string $e
}
if (\tts_is_on_error_page() === true) {
- $resource = \main_tts\configure::get('tts', 'error_page');
- \bs_tts\requires::secure_include('views/errors', $resource);
+ if (\bs_tts\requires::secure_include('prod_error.php', 'on_error') === false) {
+ \bs_tts\requires::secure_include('views/on_error/prod_error.php', 'tts');
+ }
exit(1); // Prevent HTML Looping!!!
}
$http_response_code = 307; // 307 Temporary Redirect
$prj = \main_tts\configure::get('tts', 'default_project');
- $ref = TTS_PROJECT_BASE_REF;
-
+ $ref = (defined("TTS_PROJECT_BASE_REF")) ? TTS_PROJECT_BASE_REF : false;
+ if (empty($prj) || $ref === false) {
+ echo "Sorry, we had an error...
We apologize for any inconvenience this may cause.
";
+ exit(1);
+ }
if (\bs_tts\common::is_string_found($ref, '/') === false) {
$ref .= '/';
}
diff --git a/src/bootstrap/requires.php b/src/bootstrap/requires.php
index 42780d9..9968e75 100644
--- a/src/bootstrap/requires.php
+++ b/src/bootstrap/requires.php
@@ -105,7 +105,6 @@ final class requires {
} else {
$filtered_dir = rtrim(self::filter_dir_path($dir), '/') . '/';
$file_plus_dir = $filtered_dir . $filtered_file;
- // $file_plus_dir = str_replace('_DS_', '/', $file_plus_dir);
}
$escaped_file = escapeshellcmd($file_plus_dir . $file_type);
return self::get_PHP_Version_for_file($escaped_file);
@@ -123,9 +122,9 @@ final class requires {
$dir = match ($path) {
"dir_fixed" => "",
"framework", "tts" => \main_tts\TTS_FRAMEWORK,
+ "on_error" => \bs_tts\site_helper::get_root() . \bs_tts\site_helper::get_project() . "views/on_error/",
default => \bs_tts\site_helper::get_root(), // project
};
-
$versioned_file = self::safer_file_exists($file, $dir);
if ($versioned_file === false) {
return false;
diff --git a/src/bootstrap/safer_io.php b/src/bootstrap/safer_io.php
index 0024ccb..6f895b5 100644
--- a/src/bootstrap/safer_io.php
+++ b/src/bootstrap/safer_io.php
@@ -85,7 +85,7 @@ final class safer_io {
}
// Allow anything to set_data_inputs is desired here
- public static function set_data_input(string $var_name, $data_in): void {
+ 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;
}
diff --git a/src/classes/app.php b/src/classes/app.php
index ab3b53f..1f0f5c0 100644
--- a/src/classes/app.php
+++ b/src/classes/app.php
@@ -166,6 +166,12 @@ class app {
$call_class = "\\prj\\" . rtrim($project_folder, "/") . '\\' . $test . 'controllers\\' . $class;
$controller = new $call_class();
+ if ($method === "error" && str_contains($class, "app") &&
+ method_exists($controller, $method) === false
+ ) {
+ tts_broken_error();
+ return false;
+ }
if ($use_api) {
if (empty($method)) {
diff --git a/src/classes/contracts/sessions_interface.php b/src/classes/contracts/sessions_interface.php
index 8b69ee7..ea48219 100644
--- a/src/classes/contracts/sessions_interface.php
+++ b/src/classes/contracts/sessions_interface.php
@@ -3,7 +3,7 @@
declare(strict_types=1);
namespace tts\contacts;
-
+use SessionHandlerInterface;
interface sessions_interface {
public function close(): bool;
diff --git a/src/classes/page_not_found.php b/src/classes/page_not_found.php
index fb2bdc6..673ef57 100644
--- a/src/classes/page_not_found.php
+++ b/src/classes/page_not_found.php
@@ -47,12 +47,13 @@ class page_not_found {
if ($use_api === true) {
self::api_method_not_found();
}
-
- $loaded = \bs_tts\requires::secure_include('views/404', 'tts'); // Show 404, Page Not Found Error Page!
- if ($loaded === false) {
- echo "404 Page Not Found!
";
+ // Show 404, Page Not Found Error Page!
+ if (\bs_tts\requires::secure_include('404_page.php', 'on_error') === false) {
+ $loaded = \bs_tts\requires::secure_include('views/on_error/404_page.php', 'tts');
+ if ($loaded === false) {
+ echo "404 Page Not Found!
";
+ }
}
-
exit(1);
}
diff --git a/src/classes/services/sessions/cookie_sessions.php b/src/classes/services/sessions/cookie_sessions.php
new file mode 100644
index 0000000..2a68ce6
--- /dev/null
+++ b/src/classes/services/sessions/cookie_sessions.php
@@ -0,0 +1,114 @@
+
+ * @copyright Copyright (c) 2022, Robert Strutts.
+ * @license https://mit-license.org/
+ */
+
+class cookie_sessions_handler_exception extends \Exception {}
+
+class cookie_sessions implements \SessionHandlerInterface {
+ private static int $zlib_compression_level = 4;
+ public static string $cookie_domain;
+ public static string $cookie_name = 'SES';
+ public static string $cookie_path = '/';
+ public static bool $cookie_secure = true;
+ public static bool $cookie_HTTP_only = true;
+ private $enc;
+
+ public static function set_zlib_compression_level(int $level): void {
+ self::$zlib_compression_level = ($level >= 0 && $level <= 9) ?
+ $level : 4;
+ }
+
+ public function __construct($enc) {
+ self::$cookie_domain = $_SERVER['SERVER_NAME'] ?? '';
+ $this->enc = $enc;
+ }
+
+ private function encrypt(string & $data): void {
+ $data = $this->enc->encode("sess", $data);
+ }
+
+ private function decrypt(string & $data): void {
+ try {
+ $data = $this->enc->decode("sess", $data);
+ } catch (\Exception $e) {
+ $data = false; // Maybe it has no data to decode
+ }
+ }
+
+ private function compress(string & $data): void {
+ $data = gzdeflate($data, self::$zlib_compression_level);
+ if ($data === false) {
+ throw new \tts\services\sessions\cookie_sessions_handler_exception('Failed to compress session data');
+ }
+ }
+
+ private function decompress(string & $data): void {
+ $data = gzinflate($data);
+ }
+
+ public function open($save_path, $session_name):bool {
+ return true;
+ }
+
+ public function close(): bool {
+ return true;
+ }
+
+ public function read($id): false|string {
+ $data = isset($_COOKIE[self::$cookie_name]) ? $_COOKIE[self::$cookie_name] : null;
+ if ($data === null) {
+ return "";
+ }
+
+ $data = base64_decode($data);
+ if ($data === false) {
+ return "";
+ }
+
+ $this->decrypt($data);
+ if ($data === false) {
+ return "";
+ }
+
+ $this->decompress($data);
+ return ($data !== false) ? $data : '';
+ }
+
+ public function write($id, $data): bool {
+ if (headers_sent($file, $line)) {
+ throw new \tts\services\sessions\cookie_sessions_handler_exception("Error headers were already sent by $file on line # $line ");
+ }
+
+ $this->compress($data);
+ $this->encrypt($data);
+ $data = base64_encode($data);
+ /*
+ * Google Chrome - 4096 bytes confirming to RFC.
+ * If the data is over 4000 bytes, throw an exception
+ * as web browsers only support up to 4K of Cookie Data!
+ */
+ if (strlen($data) > 4000) {
+ throw new \tts\services\sessions\cookie_sessions_handler_exception("Session data too big (over 4KB)");
+ }
+ $cookie_lifetime = (int) ini_get('session.cookie_lifetime');
+
+ return setcookie(self::$cookie_name, $data, $cookie_lifetime, self::$cookie_path, self::$cookie_domain, self::$cookie_secure, self::$cookie_HTTP_only);
+ }
+
+ public function destroy($id): bool {
+ $expiration_time = time() - 3600;
+ return setcookie(self::$cookie_name, "", $expiration_time, self::$cookie_path, self::$cookie_domain, self::$cookie_secure, self::$cookie_HTTP_only);
+ }
+
+ public function gc($max_lifetime): int|false {
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/src/classes/services/sessions/file_sessions.php b/src/classes/services/sessions/file_sessions.php
index 13a96e7..8f7706b 100644
--- a/src/classes/services/sessions/file_sessions.php
+++ b/src/classes/services/sessions/file_sessions.php
@@ -12,7 +12,7 @@ namespace tts\services\sessions;
class file_sessions implements \tts\contracts\sessions_interface {
- private $savePath;
+ private $save_path;
private function filter_id(string $id): string {
if (\bs_tts\requires::is_valid_file($id)) {
@@ -21,17 +21,16 @@ class file_sessions implements \tts\contracts\sessions_interface {
throw new \Exception('Bad ID for session!');
}
- public function open(string $savePath, string $sessionName): bool {
- $safer_dir = \bs_tts\requires::safer_dir_exists($savePath);
+ public function open(string $save_path, string $session_name): bool {
+ $safer_dir = \bs_tts\requires::safer_dir_exists($save_path);
if ($safer_dir === false) {
return false;
}
- $this->savePath = $safer_dir;
- if (!is_dir($this->savePath)) {
- mkdir($this->savePath, 0777);
+ $this->save_path = $safer_dir;
+ if (!is_dir($this->save_path)) {
+ mkdir($this->save_path, 0777);
}
-
return true;
}
@@ -39,33 +38,31 @@ class file_sessions implements \tts\contracts\sessions_interface {
return true;
}
- public function read(string $id): string {
+ public function read(string $id): false|string {
$safer_id = $this->filter_id($id);
- return (string) @file_get_contents("{$this->savePath}/sess_{$safer_id}");
+ return (string) @file_get_contents("{$this->save_path}/sess_{$safer_id}");
}
public function write(string $id, string $data): bool {
$safer_id = $this->filter_id($id);
- return file_put_contents("{$this->savePath}/sess_{$safer_id}", $data) === false ? false : true;
+ return file_put_contents("{$this->save_path}/sess_{$safer_id}", $data) === false ? false : true;
}
public function destroy(string $id): bool {
$safer_id = $this->filter_id($id);
- $file = "{$this->savePath}/sess_{$safer_id}";
+ $file = "{$this->save_path}/sess_{$safer_id}";
if (file_exists($file)) {
unlink($file);
}
-
return true;
}
- public function gc(int $maxlifetime): int {
- foreach (glob("{$this->savePath}/sess_*") as $file) {
- if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
+ public function gc(int $max_lifetime): int|false {
+ foreach (glob("{$this->save_path}/sess_*") as $file) {
+ if (filemtime($file) + $max_lifetime < time() && file_exists($file)) {
unlink($file);
}
}
-
return true;
}
diff --git a/src/classes/services/sessions/redis_sessions.php b/src/classes/services/sessions/redis_sessions.php
index aa52908..48e6f80 100644
--- a/src/classes/services/sessions/redis_sessions.php
+++ b/src/classes/services/sessions/redis_sessions.php
@@ -15,45 +15,38 @@ class redis_sessions implements \tts\contracts\sessions_interface {
protected $db;
protected $prefix;
- public function __construct(PredisClient $db, $prefix = 'PHPSESSID:') {
+ public function __construct(PredisClient $db, $prefix = 'SESS:') {
$this->db = $db;
$this->prefix = $prefix;
}
- public function open($save_path, $session_name) {
+ public function open($save_path, $session_name): bool{
// No action necessary because connection is injected
// in constructor and arguments are not applicable.
}
- public function close() {
+ public function close(): bool {
$this->db = null;
unset($this->db);
}
- public function read($id) {
+ public function read($id): false|string {
$id = $this->prefix . $id;
$sessData = $this->db->get($id);
$this->db->expire($id, $this->ttl);
return $sessData;
}
- public function write($id, $data) {
+ public function write($id, $data): bool {
$id = $this->prefix . $id;
$this->db->set($id, $data);
$this->db->expire($id, $this->ttl);
}
- public function destroy($id) {
+ public function destroy($id): bool {
$this->db->del($this->prefix . $id);
}
- public function gc($max_lifetime) {
+ public function gc($max_lifetime): int|false {
// no action necessary because using EXPIRE
}
-}
-
-/*
-$db = new PredisClient();
-$sessHandler = new redis_sessions($db);
-session_set_save_handler($sessHandler);
-session_start();
- */
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/classes/view.php b/src/classes/view.php
index 45bb8cc..4ad1b07 100644
--- a/src/classes/view.php
+++ b/src/classes/view.php
@@ -38,18 +38,13 @@ final class view {
/**
* @todo Ignore render path tts, should go prj/,...,then tts
*/
- private function get_file(string $view_file, string $default, string $render_path = 'project'): string {
+ private function get_file(string $view_file, string $default): string {
$file_ext = \bs_tts\common::get_string_right($view_file, 4);
if (! \bs_tts\common::is_string_found($file_ext, '.')) {
$view_file .= '.php';
}
- if ($render_path === 'tts') {
- $file = (empty($default)) ? "views/{$view_file}" : "views/{$default}/{$view_file}";
- $path = \main_tts\TTS_FRAMEWORK;
- } else {
- $file = (empty($default)) ? "{$this->project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
- $path = \bs_tts\site_helper::get_root();
- }
+ $file = (empty($default)) ? "{$this->project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
+ $path = \bs_tts\site_helper::get_root();
$vf = $path . $file;
if ( \bs_tts\requires::safer_file_exists($vf) !== false) {
return $file;
@@ -60,18 +55,18 @@ final class view {
/**
* Alias to set_view
*/
- public function include(string $file, string $render_path = 'project'): void {
- $this->set_view($file, $render_path);
+ public function include(string $file): void {
+ $this->set_view($file);
}
/**
* Alias to set_view
*/
- public function add_view(string $file, string $render_path = 'project'): void {
- $this->set_view($file, $render_path);
+ public function add_view(string $file): void {
+ $this->set_view($file);
}
- private function find_view_path(string $view_file, string $render_path) {
+ private function find_view_path(string $view_file) {
$found = false;
$default_paths = \main_tts\configure::get('view_mode', 'default_paths');
@@ -84,7 +79,7 @@ final class view {
}
foreach ($default_paths as $default) {
- $file = $this->get_file($view_file, $default, $render_path);
+ $file = $this->get_file($view_file, $default);
if ( ! empty($file) ) {
$found = true;
break;
@@ -93,11 +88,10 @@ final class view {
return ($found) ? $file : false;
}
- private function find_template_path($tpl_file, $render_path) {
+ private function find_template_path($tpl_file) {
$file = "{$this->project_dir}/views/includes/{$tpl_file}";
- $path = ($render_path === 'project') ? \bs_tts\site_helper::get_root() : \main_tts\TTS_FRAMEWORK;
+ $path = \bs_tts\site_helper::get_root();
$vf = $path . $file;
-
return \bs_tts\requires::safer_file_exists($vf);
}
@@ -107,7 +101,7 @@ final class view {
* @param string $render_path
* @throws Exception
*/
- public function set_view(string $view_file = null, string $render_path = 'project'): void {
+ public function set_view(string $view_file = null): void {
if ($view_file === null) {
return;
}
@@ -121,16 +115,16 @@ final class view {
}
if ($file_ext === '.php') {
- $file = $this->find_view_path($view_file, $render_path);
+ $file = $this->find_view_path($view_file);
} else {
- $file = $this->find_template_path($view_file, $render_path);
+ $file = $this->find_template_path($view_file);
}
if ($file === false) {
echo "No view file exists for: {$view_file}!";
throw new \Exception("View File does not exist: " . $view_file);
} else {
- $this->files[] = array('file'=>$file, 'path'=>$render_path, 'file_type'=>$file_ext);
+ $this->files[] = array('file'=>$file, 'path'=>"project", 'file_type'=>$file_ext);
}
}
diff --git a/src/views/default/broken.php b/src/views/default/broken.php
deleted file mode 100644
index 35830c3..0000000
--- a/src/views/default/broken.php
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-= $ex ?>
-
-
diff --git a/src/views/404.php b/src/views/on_error/404_page.php
similarity index 100%
rename from src/views/404.php
rename to src/views/on_error/404_page.php
diff --git a/src/templates/dev_error.php b/src/views/on_error/dev_error.php
similarity index 100%
rename from src/templates/dev_error.php
rename to src/views/on_error/dev_error.php
diff --git a/src/views/errors.php b/src/views/on_error/prod_error.php
similarity index 100%
rename from src/views/errors.php
rename to src/views/on_error/prod_error.php