Browse Source

Get the special component working

nsITobin 3 years ago
parent
commit
e8eda2ddd8
5 changed files with 911 additions and 421 deletions
  1. 438 0
      base/fundamentals.php
  2. 133 0
      base/special.php
  3. 25 421
      index.php
  4. 4 0
      skin/special/template-footer.xhtml
  5. 311 0
      skin/special/template-header.xhtml

+ 438 - 0
base/fundamentals.php

@@ -0,0 +1,438 @@
+<?php
+// == | Primitives | ==================================================================================================
+
+const NEW_LINE              = "\n";
+const EMPTY_STRING          = "";
+const EMPTY_ARRAY           = [];
+const SPACE                 = " ";
+const DOT                   = ".";
+const SLASH                 = "/";
+const DASH                  = "-";
+const WILDCARD              = "*";
+
+const SCHEME_SUFFIX         = "://";
+
+const PHP_EXTENSION         = DOT . 'php';
+const INI_EXTENSION         = DOT . 'ini';
+const XML_EXTENSION         = DOT . 'xml';
+const JSON_EXTENSION        = DOT . 'json';
+
+const JSON_ENCODE_FLAGS     = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+const FILE_WRITE_FLAGS      = "w+";
+const XML_TAG               = '<?xml version="1.0" encoding="utf-8" ?>';
+
+// ====================================================================================================================
+
+// == | Global Functions | ============================================================================================
+
+/**********************************************************************************************************************
+* Polyfills for missing/proposed functions
+* str_starts_with, str_ends_with, str_contains
+*
+* @param $haystack  string
+* @param $needle    substring
+* @returns          true if substring exists in string else false
+**********************************************************************************************************************/
+if (!function_exists('str_starts_with')) {
+  function str_starts_with($haystack, $needle) {
+     $length = strlen($needle);
+     return (substr($haystack, 0, $length) === $needle);
+  }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+if (!function_exists('str_ends_with')) {
+  function str_ends_with($haystack, $needle) {
+    $length = strlen($needle);
+    if ($length == 0) {
+      return true;
+    }
+
+    return (substr($haystack, -$length) === $needle);
+  }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+if (!function_exists('str_contains')) {
+  function str_contains($haystack, $needle) {
+    if (strpos($haystack, $needle) > -1) {
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+}
+
+/**********************************************************************************************************************
+* Error function that will display data (Error Message)
+**********************************************************************************************************************/
+function gfError($aValue, $phpError = false) { 
+  $pageHeader = array(
+    'default' => 'Unable to Comply',
+    'fatal'   => 'Fatal Error',
+    'php'     => 'PHP Error',
+    'output'  => 'Output'
+  );
+
+  if (is_string($aValue) || is_int($aValue)) {
+    $errorContentType = 'text/xml';
+    $errorPrefix = $phpError ? $pageHeader['php'] : $pageHeader['default'];
+    $errorMessage = XML_TAG . NEW_LINE . '<error>' . $errorPrefix . ':' . SPACE . $aValue . '</error>';
+  }
+  else {
+    $errorPrefix = $pageHeader['output'];
+    $errorContentType = 'application/json';
+    $errorMessage = json_encode($aValue, JSON_ENCODE_FLAGS);
+  }
+
+  if (function_exists('gfGenContent')) {
+    if ($phpError) {
+      gfGenContent($errorPrefix, $errorMessage, null, true, true);
+    }
+
+    gfGenContent($errorPrefix, $errorMessage);
+  }
+  else {
+    header('Content-Type: ' . $errorContentType, false);
+    print($errorMessage);
+  }
+
+  // We're done here.
+  exit();
+}
+
+/**********************************************************************************************************************
+* PHP Error Handler
+**********************************************************************************************************************/
+function gfErrorHandler($errno, $errstr, $errfile, $errline) {
+  $errorCodes = array(
+    E_ERROR               => 'Fatal Error',
+    E_WARNING             => 'Warning',
+    E_PARSE               => 'Parse',
+    E_NOTICE              => 'Notice',
+    E_CORE_ERROR          => 'Fatal Error (Core)',
+    E_CORE_WARNING        => 'Warning (Core)',
+    E_COMPILE_ERROR       => 'Fatal Error (Compile)',
+    E_COMPILE_WARNING     => 'Warning (Compile)',
+    E_USER_ERROR          => 'Fatal Error (User Generated)',
+    E_USER_WARNING        => 'Warning (User Generated)',
+    E_USER_NOTICE         => 'Notice (User Generated)',
+    E_STRICT              => 'Strict',
+    E_RECOVERABLE_ERROR   => 'Fatal Error (Recoverable)',
+    E_DEPRECATED          => 'Deprecated',
+    E_USER_DEPRECATED     => 'Deprecated (User Generated)',
+    E_ALL                 => 'All',
+  );
+
+  $errorType = $errorCodes[$errno] ?? $errno;
+  $errorMessage = $errorType . ': ' . $errstr . SPACE . 'in' . SPACE .
+                  str_replace(ROOT_PATH, '', $errfile) . SPACE . 'on line' . SPACE . $errline;
+
+  if (!(error_reporting() & $errno)) {
+    // Don't do jack shit because the developers of PHP think users shouldn't be trusted.
+    return;
+  }
+
+  gfError($errorMessage, true);
+}
+
+set_error_handler("gfErrorHandler");
+
+/**********************************************************************************************************************
+* Unified Var Checking
+*
+* @param $_type           Type of var to check
+* @param $_value          GET/SERVER/EXISTING Normal Var
+* @param $_allowFalsy     Optional - Allow falsey returns (really only works with case var)
+* @returns                Value or null
+**********************************************************************************************************************/
+function gfSuperVar($_type, $_value, $_allowFalsy = null) {
+  $errorPrefix = __FUNCTION__ . SPACE . DASH . SPACE;
+  $finalValue = null;
+
+  switch ($_type) {
+    case 'get':
+      $finalValue = $_GET[$_value] ?? null;
+
+      if ($finalValue) {
+        $finalValue = preg_replace('/[^-a-zA-Z0-9_\-\/\{\}\@\.\%\s\,]/', '', $_GET[$_value]);
+      }
+
+      break;
+    case 'post':
+      $finalValue = $_POST[$_value] ?? null;
+      break;
+    case 'server':
+      $finalValue = $_SERVER[$_value] ?? null;
+      break;
+    case 'files':
+      $finalValue = $_FILES[$_value] ?? null;
+      if ($finalValue) {
+        if (!in_array($finalValue['error'], [UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE])) {
+          gfError($errorPrefix . 'Upload of ' . $_value . ' failed with error code: ' . $finalValue['error']);
+        }
+
+        if ($finalValue['error'] == UPLOAD_ERR_NO_FILE) {
+          $finalValue = null;
+        }
+        else {
+          $finalValue['type'] = mime_content_type($finalValue['tmp_name']);
+        }
+      }
+      break;
+    case 'cookie':
+      $finalValue = $_COOKIE[$_value] ?? null;
+      break;
+    case 'var':
+      $finalValue = $_value ?? null;
+      break;
+    default:
+      gfError($errorPrefix . 'Incorrect var check');
+  }
+
+  if (!$_allowFalsy && (empty($finalValue) || $finalValue === 'none' || $finalValue === '')) {
+    return null;
+  }
+
+  return $finalValue;
+}
+
+/**********************************************************************************************************************
+* Sends HTTP Headers to client using a short name
+*
+* @param $aHeader    Short name of header
+**********************************************************************************************************************/
+function gfHeader($aHeader) {
+  $headers = array(
+    404             => 'HTTP/1.1 404 Not Found',
+    501             => 'HTTP/1.1 501 Not Implemented',
+    'text'          => 'Content-Type: text/plain',
+    'xml'           => 'Content-Type: text/xml',
+  );
+  
+  if (!headers_sent() && array_key_exists($aHeader, $headers)) {   
+    header($headers[$aHeader]);
+
+    if (in_array($aHeader, [404, 501])) {
+      exit();
+    }
+  }
+}
+
+/**********************************************************************************************************************
+* Sends HTTP Header to redirect the client to another URL
+*
+* @param $_strURL   URL to redirect to
+**********************************************************************************************************************/
+// This function sends a redirect header
+function gfRedirect($aURL) {
+	header('Location: ' . $aURL , true, 302);
+  
+  // We are done here
+  exit();
+}
+
+/**********************************************************************************************************************
+* ---
+*
+* @param $--   --
+* @param $--   --
+* @returns     --
+***********************************************************************************************************************/
+function gfExplodeString($aSeparator, $aString) {
+  $errorPrefix = __FUNCTION__ . SPACE . DASH . SPACE;
+
+  if (!is_string($aString)) {
+    gfError($errorPrefix . 'Specified string is not a string type');
+  }
+
+  if (!str_contains($aString, $aSeparator)) {
+    gfError($errorPrefix . 'String does not contain the seperator');
+  }
+
+  $explodedString = array_values(array_filter(explode($aSeparator, $aString), 'strlen'));
+
+  return $explodedString;
+}
+
+/**********************************************************************************************************************
+* ---
+*
+* @param $--   --
+* @param $--   --
+* @returns     --
+***********************************************************************************************************************/
+function gfGetDomain($aHost, $aReturnSub = null) {
+  $host = gfExplodeString(DOT, $aHost);
+  $domainSlice = $aReturnSub ? array_slice($host, 0, -2) : array_slice($host, -2, 2);
+  $domainString = implode(DOT, $domainSlice);
+  return $domainString;
+}
+
+/**********************************************************************************************************************
+* Splits a path into an indexed array of parts
+*
+* @param $aPath   URI Path
+* @returns        array of uri parts in order
+***********************************************************************************************************************/
+function gfExplodePath($aPath) {
+  if ($aPath == SLASH) {
+    return ['root'];
+  }
+
+  return gfExplodeString(SLASH, $aPath);
+}
+
+/**********************************************************************************************************************
+* Builds a path from a list of arguments
+*
+* @param        ...$aPathParts  Path Parts
+* @returns                      Path string
+***********************************************************************************************************************/
+function gfBuildPath(...$aPathParts) {
+  $path = implode(SLASH, $aPathParts);
+  $filesystem = str_starts_with($path, ROOT_PATH);
+  
+  // Add a prepending slash if this is not a filesystem path
+  if (!$filesystem) {
+    $path = SLASH . $path;
+  }
+
+  // Add a trailing slash if the last part does not contain a dot
+  // If it is a filesystem path then we will also add a trailing slash if the last part starts with a dot
+  if (!str_contains(basename($path), DOT) || ($filesystem && str_starts_with(basename($path), DOT))) {
+    $path .= SLASH;
+  }
+
+  return $path;
+}
+
+/**********************************************************************************************************************
+* ---
+*
+* @param $--   --
+* @returns     --
+***********************************************************************************************************************/
+function gfStripRootPath($aPath) {
+  return str_replace(ROOT_PATH, EMPTY_STRING, $aPath);
+}
+
+/**********************************************************************************************************************
+* Read file (decode json if the file has that extension or parse install.rdf if that is the target file)
+*
+* @param $aFile     File to read
+* @returns          file contents or array if json
+                    null if error, empty string, or empty array
+**********************************************************************************************************************/
+function gfReadFile($aFile) {
+  $file = @file_get_contents($aFile);
+
+  if (str_ends_with($aFile, JSON_EXTENSION)) {
+    $file = json_decode($file, true);
+  }
+
+  return gfSuperVar('var', $file);
+}
+
+/**********************************************************************************************************************
+* Read file from zip-type archive
+*
+* @param $aArchive  Archive to read
+* @param $aFile     File in archive
+* @returns          file contents or array if json
+                    null if error, empty string, or empty array
+**********************************************************************************************************************/
+function gfReadFileFromArchive($aArchive, $aFile) {
+  return gfReadFile('zip://' . $aArchive . "#" . $aFile);
+}
+
+/**********************************************************************************************************************
+* Write file (encodes json if the file has that extension)
+*
+* @param $aData     Data to be written
+* @param $aFile     File to write
+* @returns          true else return error string
+**********************************************************************************************************************/
+function gfWriteFile($aData, $aFile, $aRenameFile = null) {
+  if (!gfSuperVar('var', $aData)) {
+    return 'No useful data to write';
+  }
+
+  if (file_exists($aFile)) {
+    return 'File already exists';
+  }
+
+  if (str_ends_with($aFile, JSON_EXTENSION)) {
+    $aData = json_encode($aData, JSON_ENCODE_FLAGS);
+  }
+
+  $file = fopen($aFile, FILE_WRITE_FLAGS);
+  fwrite($file, $aData);
+  fclose($file);
+
+  if ($aRenameFile) {
+    rename($aFile, $aRenameFile);
+  }
+
+  return true;
+}
+
+/**********************************************************************************************************************
+* Generate a random hexadecimal string
+*
+* @param $aLength   Desired number of final chars
+* @returns          Random hexadecimal string of desired lenth
+**********************************************************************************************************************/
+function gfHexString($aLength = 40) {
+  if ($aLength <= 1) {
+    $length = 1;
+  }
+  else {
+    $length = (int)($aLength / 2);
+  }
+
+  return bin2hex(random_bytes($length));
+}
+
+/**********************************************************************************************************************
+* Basic Filter Substitution of a string
+*
+* @param $aSubsts               multi-dimensional array of keys and values to be replaced
+* @param $aString               string to operate on
+* @param $aRegEx                set to true if pcre
+* @returns                      bitwise int value representing applications
+***********************************************************************************************************************/
+function gfSubst($aSubsts, $aString, $aRegEx = null) {
+  if (!is_array($aSubsts)) {
+    gfError('$aSubsts must be an array');
+  }
+
+  if (!is_string($aString)) {
+    gfError('$aString must be a string');
+  }
+
+  $string = $aString;
+
+  if ($aRegEx) {
+    foreach ($aSubsts as $_key => $_value) {
+      $string = preg_replace('/' . $_key . '/iU', $_value, $string);
+    }
+  }
+  else {
+    foreach ($aSubsts as $_key => $_value) {
+      $string = str_replace('{%' . $_key . '}', $_value, $string);
+    }
+  }
+
+  if (!$string) {
+    gfError('Something has gone wrong with' . SPACE . __FUNCTION__);
+  }
+
+  return $string;
+}
+
+// ====================================================================================================================
+?>

+ 133 - 0
base/special.php

@@ -0,0 +1,133 @@
+<?php
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// == | Functions | ===================================================================================================
+
+/**********************************************************************************************************************
+* Basic Content Generation using the Special Component's Template
+***********************************************************************************************************************/
+function gfGenContent($aTitle, $aContent, $aTextBox = null, $aList = null, $aError = null) {
+  $templateHead = @file_get_contents('./skin/special/template-header.xhtml');
+  $templateFooter = @file_get_contents('./skin/special/template-footer.xhtml');
+
+  // Make sure the template isn't busted, if it is send a text only error as an array
+  if (!$templateHead || !$templateFooter) {
+    gfError([__FUNCTION__ . ': Special Template is busted...', $aTitle, $aContent], -1);
+  }
+
+  // Can't use both the textbox and list arguments
+  if ($aTextBox && $aList) {
+    gfError(__FUNCTION__ . ': You cannot use both textbox and list');
+  }
+
+  // Anonymous function to determin if aContent is a string-ish or not
+  $notString = function() use ($aContent) {
+    return (!is_string($aContent) && !is_int($aContent)); 
+  };
+
+  // If not a string var_export it and enable the textbox
+  if ($notString()) {
+    $aContent = var_export($aContent, true);
+    $aTextBox = true;
+    $aList = false;
+  }
+
+  // Use either a textbox or an unordered list
+  if ($aTextBox) {
+    // We are using the textbox so put aContent in there
+    $aContent = '<textarea style="width: 1195px; resize: none;" name="content" rows="36" readonly>' .
+                $aContent .
+                '</textarea>';
+  }
+  elseif ($aList) {
+    // We are using an unordered list so put aContent in there
+    $aContent = '<ul><li>' . $aContent . '</li><ul>';
+  }
+
+  // Set page title
+  $templateHead = str_replace('<title></title>',
+                  '<title>' . $aTitle . ' - ' . SOFTWARE_NAME . ' ' . SOFTWARE_VERSION . '</title>',
+                  $templateHead);
+
+  // If we are generating an error from gfError we want to clean the output buffer
+  if ($aError) {
+    ob_get_clean();
+  }
+
+  // Send an html header
+  header('Content-Type: text/html', false);
+
+  // write out the everything
+  print($templateHead . '<h2>' . $aTitle . '</h2>' . $aContent . $templateFooter);
+
+  // We're done here
+  exit();
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+/**********************************************************************************************************************
+* Checks the exploded count against the number of path parts in an exploded path and 404s it if it is greater
+***********************************************************************************************************************/
+function gfCheckPathCount($aExpectedCount) {
+  global $gaRuntime;
+  if (count($gaRuntime['explodedPath']) > $aExpectedCount) {
+    gfHeader(404);
+  }
+}
+
+// ====================================================================================================================
+
+// == | Main | ========================================================================================================
+
+// The Special Component doesn't intend on having more than one level on metropolis
+gfCheckPathCount(1);
+
+switch ($gaRuntime['explodedPath'][0]) {
+  case 'phpinfo':
+    phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_ENVIRONMENT | INFO_VARIABLES);
+    break;
+  case 'software-state':
+    gfGenContent('Software State', $gaRuntime);
+    break;
+  case 'test':
+    $gaRuntime['requestTestCase'] = gfSuperVar('get', 'case');
+    $arrayTestsGlob = glob('./base/tests/*.php');
+    $arrayFinalTests = [];
+
+    foreach ($arrayTestsGlob as $_value) {
+      $arrayFinalTests[] = str_replace('.php',
+                                       '',
+                                       str_replace('./base/tests/', '', $_value));
+    }
+
+    unset($arrayTestsGlob);
+
+    if ($gaRuntime['requestTestCase'] &&
+        in_array($gaRuntime['requestTestCase'], $arrayFinalTests)) {
+      require_once('./base/tests/' . $gaRuntime['requestTestCase'] . '.php');
+    }
+
+    $testsHTML = '';
+
+    foreach ($arrayFinalTests as $_value) {
+      $testsHTML .= '<li><a href="/test/?case=' . $_value . '">' . $_value . '</a></li>';
+    }
+
+    $testsHTML = '<ul>' . $testsHTML . '</ul>';
+
+    gfGenContent('Test Cases - Special Component', $testsHTML);
+    break;
+  case 'root':
+    $rootHTML = '<a href="/test/">Test Cases</a></li><li>' .
+                '<a href="/phpinfo/">PHP Info</a></li><li>' .
+                '<a href="/software-state/">Software State</a>';
+    gfGenContent('Special Component', $rootHTML, null, true);
+  default:
+    gfHeader(404);
+}
+
+// ====================================================================================================================
+?>

+ 25 - 421
index.php

@@ -1,434 +1,44 @@
 <?php
 // == | Setup | =======================================================================================================
 
+// Enable Error Reporting
 error_reporting(E_ALL);
 ini_set("display_errors", "on");
 
-define('DEBUG_MODE', $_GET['debug'] ?? null);
-
 // This has to be defined using the function at runtime because it is based
 // on a variable. However, constants defined with the language construct
 // can use this constant by some strange voodoo. Keep an eye on this.
 // NOTE: DOCUMENT_ROOT does NOT have a trailing slash.
 define('ROOT_PATH', $_SERVER['DOCUMENT_ROOT']);
 
-const NEW_LINE              = "\n";
-const EMPTY_STRING          = "";
-const EMPTY_ARRAY           = [];
-const SPACE                 = " ";
-const DOT                   = ".";
-const SLASH                 = "/";
-const DASH                  = "-";
-const WILDCARD              = "*";
+// Debug flag
+define('DEBUG_MODE', $_GET['debug'] ?? null);
 
-const JSON_EXTENSION        = DOT . 'json';
+// Load fundamental constants and global functions
+// This is so we can arbitrarily reuse them in adhoc situations which is why they are not following
+// the globalConstants/globalFunctions scheme used by other BinOC Applications.
+require_once('./base/fundamentals.php');
 
-const XML_TAG               = '<?xml version="1.0" encoding="utf-8" ?>';
+const SOFTWARE_NAME       = 'Metropolis';
+const SOFTWARE_VERSION    = '1.0.0a1';
 
-const JSON_ENCODE_FLAGS     = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
-const FILE_WRITE_FLAGS      = "w+";
+const HTTPS_SCHEME          = 'https' . SCHEME_SUFFIX;
+const BINOC_DOMAIN          = 'binaryoutcast.com';
 
 // ====================================================================================================================
 
-// == | Global Functions | ============================================================================================
-
-/**********************************************************************************************************************
-* Polyfills for missing/proposed functions
-* str_starts_with, str_ends_with, str_contains
-*
-* @param $haystack  string
-* @param $needle    substring
-* @returns          true if substring exists in string else false
-**********************************************************************************************************************/
-if (!function_exists('str_starts_with')) {
-  function str_starts_with($haystack, $needle) {
-     $length = strlen($needle);
-     return (substr($haystack, 0, $length) === $needle);
-  }
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-
-if (!function_exists('str_ends_with')) {
-  function str_ends_with($haystack, $needle) {
-    $length = strlen($needle);
-    if ($length == 0) {
-      return true;
-    }
-
-    return (substr($haystack, -$length) === $needle);
-  }
-}
-
-// --------------------------------------------------------------------------------------------------------------------
-
-if (!function_exists('str_contains')) {
-  function str_contains($haystack, $needle) {
-    if (strpos($haystack, $needle) > -1) {
-      return true;
-    }
-    else {
-      return false;
-    }
-  }
-}
-
-/**********************************************************************************************************************
-* Error function that will display data (Error Message)
-**********************************************************************************************************************/
-function gfError($aValue, $phpError = false) { 
-  if (is_string($aValue) || is_int($aValue)) {
-    $errorContentType = 'text/xml';
-    $errorPrefix = $phpError ? 'PHP' : 'Unable to Comply';
-    $errorMessage = XML_TAG . NEW_LINE . '<error>' . $errorPrefix . ':' . SPACE . $aValue . '</error>';
-  }
-  else {
-    $errorContentType = 'application/json';
-    $errorMessage = json_encode($aValue, JSON_ENCODE_FLAGS);
-  }
-
-  if (function_exists('gfGenContent')) {
-    gfGenContent($errorMessage, true);
-  }
-  else {
-    header('Content-Type: ' . $errorContentType, false);
-    print($errorMessage);
-  }
-
-  // We're done here.
-  exit();
-}
-
-/**********************************************************************************************************************
-* PHP Error Handler
-**********************************************************************************************************************/
-function gfErrorHandler($errno, $errstr, $errfile, $errline) {
-  $errorCodes = array(
-    E_ERROR               => 'Fatal Error',
-    E_WARNING             => 'Warning',
-    E_PARSE               => 'Parse',
-    E_NOTICE              => 'Notice',
-    E_CORE_ERROR          => 'Fatal Error (Core)',
-    E_CORE_WARNING        => 'Warning (Core)',
-    E_COMPILE_ERROR       => 'Fatal Error (Compile)',
-    E_COMPILE_WARNING     => 'Warning (Compile)',
-    E_USER_ERROR          => 'Fatal Error (User Generated)',
-    E_USER_WARNING        => 'Warning (User Generated)',
-    E_USER_NOTICE         => 'Notice (User Generated)',
-    E_STRICT              => 'Strict',
-    E_RECOVERABLE_ERROR   => 'Fatal Error (Recoverable)',
-    E_DEPRECATED          => 'Deprecated',
-    E_USER_DEPRECATED     => 'Deprecated (User Generated)',
-    E_ALL                 => 'All',
-  );
-
-  $errorType = $errorCodes[$errno] ?? $errno;
-  $errorMessage = $errorType . ': ' . $errstr . SPACE . 'in' . SPACE .
-                  str_replace(ROOT_PATH, '', $errfile) . SPACE . 'on line' . SPACE . $errline;
-
-  if (!(error_reporting() & $errno)) {
-    // Don't do jack shit because the developers of PHP think users shouldn't be trusted.
-    return;
-  }
-
-  gfError($errorMessage, 1);
-}
-
-set_error_handler("gfErrorHandler");
-
-/**********************************************************************************************************************
-* Unified Var Checking
-*
-* @param $_type           Type of var to check
-* @param $_value          GET/SERVER/EXISTING Normal Var
-* @param $_allowFalsy     Optional - Allow falsey returns (really only works with case var)
-* @returns                Value or null
-**********************************************************************************************************************/
-function gfSuperVar($_type, $_value, $_allowFalsy = null) {
-  $errorPrefix = __FUNCTION__ . SPACE . DASH . SPACE;
-  $finalValue = null;
-
-  switch ($_type) {
-    case 'get':
-      $finalValue = $_GET[$_value] ?? null;
-
-      if ($finalValue) {
-        $finalValue = preg_replace('/[^-a-zA-Z0-9_\-\/\{\}\@\.\%\s\,]/', '', $_GET[$_value]);
-      }
-
-      break;
-    case 'post':
-      $finalValue = $_POST[$_value] ?? null;
-      break;
-    case 'server':
-      $finalValue = $_SERVER[$_value] ?? null;
-      break;
-    case 'files':
-      $finalValue = $_FILES[$_value] ?? null;
-      if ($finalValue) {
-        if (!in_array($finalValue['error'], [UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE])) {
-          gfError($errorPrefix . 'Upload of ' . $_value . ' failed with error code: ' . $finalValue['error']);
-        }
-
-        if ($finalValue['error'] == UPLOAD_ERR_NO_FILE) {
-          $finalValue = null;
-        }
-        else {
-          $finalValue['type'] = mime_content_type($finalValue['tmp_name']);
-        }
-      }
-      break;
-    case 'cookie':
-      $finalValue = $_COOKIE[$_value] ?? null;
-      break;
-    case 'var':
-      $finalValue = $_value ?? null;
-      break;
-    default:
-      gfError($errorPrefix . 'Incorrect var check');
-  }
-
-  if (!$_allowFalsy && (empty($finalValue) || $finalValue === 'none' || $finalValue === '')) {
-    return null;
-  }
-
-  return $finalValue;
-}
-
-/**********************************************************************************************************************
-* Sends HTTP Headers to client using a short name
-*
-* @param $aHeader    Short name of header
-**********************************************************************************************************************/
-function gfHeader($aHeader) {
-  $headers = array(
-    404             => 'HTTP/1.1 404 Not Found',
-    501             => 'HTTP/1.1 501 Not Implemented',
-    'text'          => 'Content-Type: text/plain',
-    'xml'           => 'Content-Type: text/xml',
-  );
-  
-  if (!headers_sent() && array_key_exists($aHeader, $headers)) {   
-    header($headers[$aHeader]);
-
-    if (in_array($aHeader, [404, 501])) {
-      exit();
-    }
-  }
-}
+// == | Functions | ===================================================================================================
 
 /**********************************************************************************************************************
-* Sends HTTP Header to redirect the client to another URL
+* Adhoc load a "component" and ensure that the script exits after execution if not terminated earlier
 *
-* @param $_strURL   URL to redirect to
+* @param $aComponentPath  string
 **********************************************************************************************************************/
-// This function sends a redirect header
-function gfRedirect($aURL) {
-	header('Location: ' . $aURL , true, 302);
-  
-  // We are done here
+function gfLoadComponent($aComponentPath) {
+  require_once($aComponent);
   exit();
 }
 
-/**********************************************************************************************************************
-* ---
-*
-* @param $--   --
-* @param $--   --
-* @returns     --
-***********************************************************************************************************************/
-function gfExplodeString($aSeparator, $aString) {
-  $errorPrefix = __FUNCTION__ . SPACE . DASH . SPACE;
-
-  if (!is_string($aString)) {
-    gfError($errorPrefix . 'Specified string is not a string type');
-  }
-
-  if (!str_contains($aString, $aSeparator)) {
-    gfError($errorPrefix . 'String does not contain the seperator');
-  }
-
-  $explodedString = array_values(array_filter(explode($aSeparator, $aString), 'strlen'));
-
-  return $explodedString;
-}
-
-/**********************************************************************************************************************
-* ---
-*
-* @param $--   --
-* @param $--   --
-* @returns     --
-***********************************************************************************************************************/
-function gfGetDomain($aHost, $aReturnSub = null) {
-  $host = gfExplodeString(DOT, $aHost);
-  $domainSlice = $aReturnSub ? array_slice($host, 0, -2) : array_slice($host, -2, 2);
-  $domainString = implode(DOT, $domainSlice);
-  return $domainString;
-}
-
-/**********************************************************************************************************************
-* Splits a path into an indexed array of parts
-*
-* @param $aPath   URI Path
-* @returns        array of uri parts in order
-***********************************************************************************************************************/
-function gfExplodePath($aPath) {
-  if ($aPath == SLASH) {
-    return ['root'];
-  }
-
-  return gfExplodeString(SLASH, $aPath);
-}
-
-/**********************************************************************************************************************
-* Builds a path from a list of arguments
-*
-* @param        ...$aPathParts  Path Parts
-* @returns                      Path string
-***********************************************************************************************************************/
-function gfBuildPath(...$aPathParts) {
-  $path = implode(SLASH, $aPathParts);
-  $filesystem = str_starts_with($path, ROOT_PATH);
-  
-  // Add a prepending slash if this is not a filesystem path
-  if (!$filesystem) {
-    $path = SLASH . $path;
-  }
-
-  // Add a trailing slash if the last part does not contain a dot
-  // If it is a filesystem path then we will also add a trailing slash if the last part starts with a dot
-  if (!str_contains(basename($path), DOT) || ($filesystem && str_starts_with(basename($path), DOT))) {
-    $path .= SLASH;
-  }
-
-  return $path;
-}
-
-/**********************************************************************************************************************
-* ---
-*
-* @param $--   --
-* @returns     --
-***********************************************************************************************************************/
-function gfStripRootPath($aPath) {
-  return str_replace(ROOT_PATH, EMPTY_STRING, $aPath);
-}
-
-/**********************************************************************************************************************
-* Read file (decode json if the file has that extension or parse install.rdf if that is the target file)
-*
-* @param $aFile     File to read
-* @returns          file contents or array if json
-                    null if error, empty string, or empty array
-**********************************************************************************************************************/
-function gfReadFile($aFile) {
-  $file = @file_get_contents($aFile);
-
-  if (str_ends_with($aFile, JSON_EXTENSION)) {
-    $file = json_decode($file, true);
-  }
-
-  return gfSuperVar('var', $file);
-}
-
-/**********************************************************************************************************************
-* Read file from zip-type archive
-*
-* @param $aArchive  Archive to read
-* @param $aFile     File in archive
-* @returns          file contents or array if json
-                    null if error, empty string, or empty array
-**********************************************************************************************************************/
-function gfReadFileFromArchive($aArchive, $aFile) {
-  return gfReadFile('zip://' . $aArchive . "#" . $aFile);
-}
-
-/**********************************************************************************************************************
-* Write file (encodes json if the file has that extension)
-*
-* @param $aData     Data to be written
-* @param $aFile     File to write
-* @returns          true else return error string
-**********************************************************************************************************************/
-function gfWriteFile($aData, $aFile, $aRenameFile = null) {
-  if (!gfSuperVar('var', $aData)) {
-    return 'No useful data to write';
-  }
-
-  if (file_exists($aFile)) {
-    return 'File already exists';
-  }
-
-  if (str_ends_with($aFile, JSON_EXTENSION)) {
-    $aData = json_encode($aData, JSON_ENCODE_FLAGS);
-  }
-
-  $file = fopen($aFile, FILE_WRITE_FLAGS);
-  fwrite($file, $aData);
-  fclose($file);
-
-  if ($aRenameFile) {
-    rename($aFile, $aRenameFile);
-  }
-
-  return true;
-}
-
-/**********************************************************************************************************************
-* Generate a random hexadecimal string
-*
-* @param $aLength   Desired number of final chars
-* @returns          Random hexadecimal string of desired lenth
-**********************************************************************************************************************/
-function gfHexString($aLength = 40) {
-  if ($aLength <= 1) {
-    $length = 1;
-  }
-  else {
-    $length = (int)($aLength / 2);
-  }
-
-  return bin2hex(random_bytes($length));
-}
-
-/**********************************************************************************************************************
-* Basic Filter Substitution of a string
-*
-* @param $aSubsts               multi-dimensional array of keys and values to be replaced
-* @param $aString               string to operate on
-* @param $aRegEx                set to true if pcre
-* @returns                      bitwise int value representing applications
-***********************************************************************************************************************/
-function gfSubst($aSubsts, $aString, $aRegEx = null) {
-  if (!is_array($aSubsts)) {
-    gfError('$aSubsts must be an array');
-  }
-
-  if (!is_string($aString)) {
-    gfError('$aString must be a string');
-  }
-
-  $string = $aString;
-
-  if ($aRegEx) {
-    foreach ($aSubsts as $_key => $_value) {
-      $string = preg_replace('/' . $_key . '/iU', $_value, $string);
-    }
-  }
-  else {
-    foreach ($aSubsts as $_key => $_value) {
-      $string = str_replace('{%' . $_key . '}', $_value, $string);
-    }
-  }
-
-  if (!$string) {
-    gfError('Something has gone wrong with' . SPACE . __FUNCTION__);
-  }
-
-  return $string;
-}
-
 // ====================================================================================================================
 
 // == | Main | ========================================================================================================
@@ -476,17 +86,13 @@ $gaRuntime['currentSubDomain'] = gfSuperVar('var', gfGetDomain($gaRuntime['phpSe
 // Perform actions based on the domain or optionally the subdomain
 switch ($gaRuntime['currentDomain']) {
   case 'fossamail.org':
-    gfRedirect('https://binaryoutcast.com/projects/interlink/');
-  case 'binaryoutcast.com':
+    gfRedirect(HTTPS_SCHEME . BINOC_DOMAIN . gfBuildPath('projects', 'interlink'));
+  case BINOC_DOMAIN:
     switch ($gaRuntime['currentSubDomain']) {
       case 'metropolis':
-        gfheader(501);
-        /*
         $gaRuntime['qComponent'] = 'special';
-        require_once(gfBuildPath(ROOT_PATH, 'base', 'special.php');
+        require_once(gfBuildPath(ROOT_PATH, 'base', $gaRuntime['qComponent'] . PHP_EXTENSION));
         exit();
-        */
-        break;
       case 'go':
         $gaRuntime['qDirect'] = $_GET['direct'] ?? null;
         $gaRuntime['qAlias'] = gfSuperVar('get', 'alias');
@@ -503,22 +109,20 @@ switch ($gaRuntime['currentDomain']) {
         gfHeader(404);
       case 'repository':
           if (str_starts_with($gaRuntime['qPath'], '/projects/interlink')) {
-            gfRedirect($gaRuntime['currentScheme'] .
-                       '://projects.binaryoutcast.com' .
-                       str_replace('/projects', '', $gaRuntime['qPath']));
+            gfRedirect($gaRuntime['currentScheme'] . SCHEME_SUFFIX .
+                       'projects' . BINOC_DOMAIN .
+                       str_replace(HTTPS_SCHEME . BINOC_DOMAIN . SLASH . 'projects', '', $gaRuntime['qPath']));
           }
           else {
-            gfRedirect('https://binaryoutcast.com/');
+            gfRedirect(HTTPS_SCHEME . BINOC_DOMAIN);
           }
         break;
-      case 'irc': gfRedirect('https://binaryoutcast.com/interact/');
+      case 'irc': gfRedirect(HTTPS_SCHEME . BINOC_DOMAIN . SLASH . 'interact' . SLASH);
       case 'git': gfRedirect('https://repo.palemoon.org/binaryoutcast' . $gaRuntime['qPath']);
       case 'interlink-addons': gfRedirect('https://addons.binaryoutcast.com/interlink' . $gaRuntime['qPath']);
     }
-  case 'binocnetwork.com':
-  case 'mattatobin.com':
   default:
-    gfRedirect('https://binaryoutcast.com/');
+    gfRedirect(HTTPS_SCHEME . BINOC_DOMAIN);
 }
 
 // ====================================================================================================================

+ 4 - 0
skin/special/template-footer.xhtml

@@ -0,0 +1,4 @@
+      </div>
+    </div>
+  </body>
+</html>

+ 311 - 0
skin/special/template-header.xhtml

@@ -0,0 +1,311 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title></title>
+    <style type="text/css">
+      body {
+        margin-top: 1em;
+        padding: 3em;
+        background: #f0f0f0;
+        color: #000;
+        font-size: 12px;
+        font-family: message-box, "Segoe UI", sans-serif;
+      }
+
+      #container {
+        margin: 0 auto;
+        padding: 30px;
+        width: 1200px;
+        min-width: 1200px;
+        max-width: 1200px;
+        border: 1px solid #a0a0a0;
+        border-radius: 10px;
+        background: #fff
+      }
+
+      /*
+      h1{
+        margin-bottom: 3px;
+      }
+      */
+
+      h1 .small {
+        font-size: .4em;
+      }
+
+      h1 a {
+        text-decoration: none;
+      }
+
+      h2 {
+        color: #000;
+        font-size: 1.5em;
+        border-bottom: 1px solid #ccc
+      }
+
+      h3 {
+        color: #000;
+        text-align: center;
+      }
+
+      a {
+        color: #000;
+      }
+
+      .description {
+        margin-bottom: 30px;
+        font-style: italic;
+        /*
+        margin-top:30px;
+        */  
+        font-size: 1.2em;
+      }
+
+      .download {
+        float: right;
+      }
+
+      pre {
+        font-family: "Courier New",Courier,monospace;
+        white-space: pre-wrap;
+        min-width: 300px;
+        max-width: 930px;
+      }
+
+      hr {
+        width: 80%;
+        border-bottom: 1px solid #aaa;
+      }
+
+      .strike {
+        text-decoration: line-through;
+      }
+
+      .hidden {
+        display: none;
+      }
+
+      .footer {
+        padding-top: 30px;
+        text-align: center;
+        font-style: italic;
+      }
+
+      nav {
+        display: flex;
+        justify-content: flex-start;
+      }
+
+      nav a {
+        margin-left: 20px;
+        margin-right: 20px;
+      }
+
+      img {
+        border: 0
+      }
+
+      img.favicon {
+        margin-top: 1px;
+        padding-right: 5px;
+        width: 16px;
+        height: 16px;
+        vertical-align: text-bottom;
+      }
+
+      img.textbottom {
+        vertical-align: text-bottom;
+      }
+
+      .menu {
+        margin-left: 0;
+        width: 100%;
+      }
+
+      .mozmenu {
+        /* background: url('/skin/default/logo.png') no-repeat, linear-gradient(to bottom, #455372 0%, #445271 35%, #445271 60%, #2a3753 100%); */
+        background: linear-gradient(to bottom, #455372 0%, #445271 35%, #445271 60%, #2a3753 100%);
+        height: 70px;
+        border-radius: 8px;
+        margin-top: -8px;
+        margin-bottom: 8px;
+      }
+
+      .mozmenu ul li {
+        height: 26px;
+        min-height: 26px;
+        max-height: 26px;
+        float: right;
+        list-style-type: none;
+        margin: 0;
+        padding: 0;
+        color: white;
+        font-weight: bold;
+        font-size: 14px;
+        margin-top: 41px;
+        margin-right: 1px;
+        padding: 2px 8px 0px 8px;
+        background-color: #697388;
+        border-top-left-radius: 10px;
+        border-top-right-radius: 10px;
+        border-bottom: 1px solid #515358;
+      }
+
+      .mozmenu ul:last-child {
+        margin-right: 16px;
+      }
+
+      .mozmenu ul li a,
+      .mozmenu ul li a:hover,
+      .mozmenu ul li a:active,
+      .mozmenu ul li a:visited {
+        color: white;
+        text-decoration: none;
+      }
+
+      .mozmenu ul li:hover {
+        background-color: #747f96;
+      }
+
+      a.active {
+        font-weight: 700;
+      }
+
+      a.download-stable {
+        padding: 7px 10px 8px;
+        border: 1px solid #63a62f;
+        border-bottom: 1px solid #5b992b;
+        border-radius: 3px;
+        background-color: #7fbf4d;
+        background-image: linear-gradient(to bottom, #7fbf4d, #63a62f);
+        box-shadow: inset 0 1px 0 0 #96ca6d;
+        color: #fff;
+        text-align: center;
+        text-decoration: none;
+        text-shadow: 0 -1px 0 #4c9021;
+        font-weight: 700;
+        font-size: 16px;
+        line-height: 1;
+      }
+
+      a.download-stable:active, a.download-beta:active {
+        border: 1px solid #5b992b;
+        border-bottom: 1px solid #538c27;
+        box-shadow: inset 0 0 8px 4px #548c29, 0 1px 0 0 #eee;
+      }
+
+      a.download-stable:hover {
+        background-color: #76b347;
+        background-image: linear-gradient(to bottom, #76b347, #5e9e2e);
+        box-shadow: inset 0 1px 0 0 #8dbf67;
+        cursor: pointer;
+      }
+
+      a.download-beta {
+        padding: 7px 10px 8px;
+        border: 1px solid #63a62f;
+        border-top: 1px solid #FFF;
+        border-bottom: 1px solid #5b992b;
+        border-radius: 3px;
+        background-color: #FFD203;
+        background-image: repeating-linear-gradient(-45deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.3) 5px, rgba(0, 0, 0, 0.1) 5px, rgba(0, 0, 0, 0.1) 10px);
+        box-shadow: inset 0 1px 0 0 #96ca6d;
+        color: #000;
+        text-align: center;
+        text-decoration: none;
+        text-shadow: 0 -1px 0 #ccc;
+        font-size: 16px;
+        line-height: 1;
+      }
+
+      a.download-beta:hover {
+        background-color: #ffd203;
+        background-image: repeating-linear-gradient(-45deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.3) 5px, rgba(0, 0, 0, 0.2) 5px, rgba(0, 0, 0, 0.2) 10px);
+        /* (-45deg, #F1B802, #FFD203) */
+        
+        box-shadow: inset 0 1px 0 0 #8dbf67;
+        cursor: pointer;
+      }
+
+      .pulseText {
+        animation: pulse 2000ms infinite;
+      }
+
+      @keyframes pulse {
+        50% {
+          color: red;
+        }
+      }
+
+      .fadeIn {
+        animation: fade 0.4s;
+      }
+
+      @keyframes fade {
+        from { opacity: 0; }
+        to { opacity: 1; }
+      }
+
+      /* Add-on Table */
+
+      .addonTable {
+        width: 100%;
+        border-spacing: 0px 0px;
+      }
+
+      .addonTable th {
+        text-align: left;
+      }
+
+      .addonTable tr {
+        padding-top: 2px;
+        padding-bottom: 2px;
+      }
+
+      .single-row tr:nth-child(odd) {
+        background:#eee;
+      }
+
+      .double-row tr:nth-child(4n+0), tr:nth-child(4n+1) {
+        background:#eee;
+      }
+
+      .addonTable tr:first-child {
+          background: #2a3753;
+          color: #fff;
+      }
+
+      .addonTable[metadata] tr td {
+        height: 26px;
+      }
+
+      .addonTable[metadata] tr td:first-child {
+        width: 125px;
+        font-weight: bold;
+      }
+
+      .cell-ellipsis {
+        width: 225px;
+        min-width: 225px;
+        max-width: 225px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .aligntop {
+        vertical-align: top;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="container">
+      <div class="mozmenu">
+        <ul>
+          <li><a href="/">Root</a></li>
+          <li><a href="#" onclick="window.history.back();">Go Back</a></li>
+        <ul>
+      </div>
+      <div id="content" class="fadeIn">