Merge branch 'development' into 'master'

Release 1.2.0

See merge request fuzeworks/webcomponent!2
This commit is contained in:
Abel Hoogeveen 2019-09-21 18:28:47 +00:00
commit 0ba98f8130
14 changed files with 934 additions and 329 deletions

View File

@ -14,17 +14,15 @@
], ],
"require": { "require": {
"php": ">=7.1.0", "php": ">=7.1.0",
"fuzeworks/core": "dev-development", "fuzeworks/mvcr": "~1.2.0",
"fuzeworks/mvcr": "dev-master" "fuzeworks/core": "~1.2.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7", "phpunit/phpunit": "^7"
"fuzeworks/tracycomponent": "dev-master"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"FuzeWorks\\": "src/FuzeWorks/" "FuzeWorks\\": "src/FuzeWorks/"
} }
} }
} }

View File

@ -49,56 +49,16 @@ return [
| 'csrf_regenerate' = Regenerate token on every submission | 'csrf_regenerate' = Regenerate token on every submission
| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks | 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks
*/ */
'csrf_protection' => false, 'csrf_protection' => true,
'csrf_token_name' => 'fw_csrf_token', 'csrf_token_name' => 'fw_csrf_token',
'csrf_cookie_name' => 'fw_csrf_cookie',
'csrf_expire' => 7200, 'csrf_expire' => 7200,
'csrf_regenerate' => TRUE,
'csrf_exclude_uris' => array(), 'csrf_exclude_uris' => array(),
/* // CSRF Cookie information
|-------------------------------------------------------------------------- 'csrf_cookie_name' => 'fw_csrf_cookie',
| Standardize newlines 'csrf_cookie_prefix' => '',
|-------------------------------------------------------------------------- 'csrf_cookie_domain' => '',
| 'csrf_cookie_path' => '/',
| Determines whether to standardize newline characters in input data, 'csrf_cookie_secure' => false,
| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value. 'csrf_cookie_httponly' => false
|
| This is particularly useful for portability between UNIX-based OSes,
| (usually \n) and Windows (\r\n).
|
*/
'standardize_newlines' => FALSE,
/*
|--------------------------------------------------------------------------
| Global XSS Filtering
|--------------------------------------------------------------------------
|
| Determines whether the XSS filter is always active when GET, POST or
| COOKIE data is encountered
|
| WARNING: This feature is DEPRECATED and currently available only
| for backwards compatibility purposes!
|
*/
'global_xss_filtering' => FALSE,
/*
|--------------------------------------------------------------------------
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
| If your server is behind a reverse proxy, you must whitelist the proxy
| IP addresses from which CodeIgniter should trust headers such as
| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
| the visitor's IP address.
|
| You can use both an array or a comma-separated list of proxy addresses,
| as well as specifying whole subnets. Here are a few examples:
|
| Comma-separated: '10.0.1.200,192.168.5.0/24'
| Array: array('10.0.1.200', '192.168.5.0/24')
*/
'proxy_ips' => ''
]; ];

View File

@ -0,0 +1,63 @@
<?php
/**
* FuzeWorks WebComponent.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Event;
use FuzeWorks\Event;
class ResourceServeEvent extends Event
{
/**
* @var string
*/
public $resourceName;
/**
* @var array
*/
public $resourceUrlSegments;
/**
* @var string
*/
public $resourceFilePath;
public function init(string $resourceName, array $resourceUrlSegments, string $resourceFilePath)
{
$this->resourceName = $resourceName;
$this->resourceUrlSegments = $resourceUrlSegments;
$this->resourceFilePath = $resourceFilePath;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* FuzeWorks WebComponent.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Event;
use FuzeWorks\Event;
class RouteWebRequestEvent extends Event
{
/**
* @var string
*/
public $uriString;
public function init(string $uriString)
{
$this->uriString = $uriString;
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* FuzeWorks Component.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks\Exception;
class CSRFException extends SecurityException
{
}

View File

@ -1,4 +1,4 @@
<?php /** @noinspection ALL */ <?php
/** /**
* FuzeWorks WebComponent. * FuzeWorks WebComponent.
@ -38,8 +38,10 @@
namespace FuzeWorks; namespace FuzeWorks;
use FuzeWorks\ConfigORM\ConfigORM; use FuzeWorks\ConfigORM\ConfigORM;
use Tracy\Debugger;
/**
* @todo Implement remaining methods from OldInput
*/
class Input class Input
{ {
/** /**
@ -69,6 +71,13 @@ class Input
// Set the configuration // Set the configuration
$this->webConfig = Factory::getInstance()->config->getConfig('web'); $this->webConfig = Factory::getInstance()->config->getConfig('web');
// If not handling requests, do not continue
if (!WebComponent::$willHandleRequest)
return;
// Start session
session_start();
// Sanitize all global arrays // Sanitize all global arrays
$this->sanitizeGlobals(); $this->sanitizeGlobals();
@ -76,8 +85,8 @@ class Input
{ {
if (class_exists('\FuzeWorks\TracyComponent', true) && \FuzeWorks\TracyComponent::isEnabled()) if (class_exists('\FuzeWorks\TracyComponent', true) && \FuzeWorks\TracyComponent::isEnabled())
{ {
set_exception_handler([$this, 'tracyExceptionHandler']); Core::addExceptionHandler([$this, 'restoreGlobalArrays'], Priority::HIGHEST);
set_error_handler([$this, 'tracyErrorHandler']); Core::addErrorHandler([$this, 'restoreGlobalArrays'], Priority::HIGHEST);
} }
Events::addListener( Events::addListener(
[$this, 'restoreGlobalArrays'], [$this, 'restoreGlobalArrays'],
@ -87,35 +96,10 @@ class Input
} }
/** /**
* Used to restore global arrays before handling errors by Tracy * Restores global arrays before handling by processes outside of FuzeWorks
* *
* @param $exception
* @param bool $exit
* @internal * @internal
*/ */
public function tracyExceptionHandler($exception, $exit = true)
{
$this->restoreGlobalArrays();
Debugger::exceptionHandler($exception, $exit);
}
/**
* Used to restore global arrays before handling errors by Tracy
*
* @param $severity
* @param $message
* @param $file
* @param $line
* @param array $context
* @throws \ErrorException
* @internal
*/
public function tracyErrorHandler($severity, $message, $file, $line, $context = [])
{
$this->restoreGlobalArrays();
Debugger::errorHandler($severity, $message, $file, $line, $context);
}
public function restoreGlobalArrays() public function restoreGlobalArrays()
{ {
Logger::logInfo('Restoring global $_GET, $_POST, $_SERVER, $_COOKIE arrays'); Logger::logInfo('Restoring global $_GET, $_POST, $_SERVER, $_COOKIE arrays');
@ -235,6 +219,8 @@ class Input
} }
/** /**
* Used to fetch variables from the global arrays
*
* @param string $arrayName * @param string $arrayName
* @param null $index * @param null $index
* @param bool $xssClean * @param bool $xssClean
@ -242,8 +228,8 @@ class Input
*/ */
protected function getFromInputArray(string $arrayName, $index = null, bool $xssClean = true) protected function getFromInputArray(string $arrayName, $index = null, bool $xssClean = true)
{ {
// Clean XSS if requested manually or forced through configuration // Never run XSS clean if disabled by config
$xssClean = $xssClean || $this->webConfig->get('xss_clean'); $xssClean = ($this->webConfig->get('xss_clean') == true ? $xssClean : false);
// If the index is null, the entire array is requested // If the index is null, the entire array is requested
$index = (!is_null($index) ? $index : array_keys($this->inputArray[$arrayName])); $index = (!is_null($index) ? $index : array_keys($this->inputArray[$arrayName]));
@ -266,59 +252,124 @@ class Input
return ($xssClean === true ? $this->security->xss_clean($value) : $value); return ($xssClean === true ? $this->security->xss_clean($value) : $value);
} }
/**
* Fetch variables from the global $_GET array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function get($index = null, bool $xssClean = true) public function get($index = null, bool $xssClean = true)
{ {
return $this->getFromInputArray('get', $index, $xssClean); return $this->getFromInputArray('get', $index, $xssClean);
} }
/**
* Fetch variables from the global $_POST array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function post($index = null, bool $xssClean = true) public function post($index = null, bool $xssClean = true)
{ {
return $this->getFromInputArray('post', $index, $xssClean); return $this->getFromInputArray('post', $index, $xssClean);
} }
/**
* Fetch variables from the global $_POST or $_GET array. Tries POST first
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function postGet($index, bool $xssClean = true) public function postGet($index, bool $xssClean = true)
{ {
return isset($this->inputArray['post'][$index]) ? $this->post($index, $xssClean) : $this->get($index, $xssClean); return isset($this->inputArray['post'][$index]) ? $this->post($index, $xssClean) : $this->get($index, $xssClean);
} }
/**
* Fetch variables from the global $_GET or $_POST array. Tries GET first
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function getPost($index, bool $xssClean = true) public function getPost($index, bool $xssClean = true)
{ {
return isset($this->inputArray['get'][$index]) ? $this->get($index, $xssClean) : $this->post($index, $xssClean); return isset($this->inputArray['get'][$index]) ? $this->get($index, $xssClean) : $this->post($index, $xssClean);
} }
/**
* Fetch variables from the global $_COOKIE array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function cookie($index = null, bool $xssClean = true) public function cookie($index = null, bool $xssClean = true)
{ {
return $this->getFromInputArray('cookie', $index, $xssClean); return $this->getFromInputArray('cookie', $index, $xssClean);
} }
/**
* Fetch variables from the global $_SERVER array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function server($index = null, bool $xssClean = true) public function server($index = null, bool $xssClean = true)
{ {
return $this->getFromInputArray('server', $index, $xssClean); return $this->getFromInputArray('server', $index, $xssClean);
} }
/** /**
* @todo Extend with OldInput functionality * Fetch the HTTP_USER_AGENT variable from the $_SERVER array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/ */
public function ip()
{
$ip = '';
// Validate IP
$valid = (
(bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ||
(bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)
);
}
public function userAgent(bool $xssClean = true): string public function userAgent(bool $xssClean = true): string
{ {
return $this->getFromInputArray('server', 'HTTP_USER_AGENT', $xssClean); return $this->getFromInputArray('server', 'HTTP_USER_AGENT', $xssClean);
} }
/**
* Fetch the REQUEST_METHOD variable from the $_SERVER array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
public function method(bool $xssClean = true): string public function method(bool $xssClean = true): string
{ {
return $this->getFromInputArray('server', 'REQUEST_METHOD', $xssClean); return $this->getFromInputArray('server', 'REQUEST_METHOD', $xssClean);
} }
/**
* Is HTTPS?
*
* Determines if the application is accessed via an encrypted
* (HTTPS) connection.
*
* @return bool
*/
public function isHttps(): bool
{
if (!empty($this->inputArray['server']['HTTPS']) && strtolower($this->inputArray['server']['HTTPS']) !== 'off')
return true;
elseif (isset($this->inputArray['server']['HTTP_X_FORWARDED_PROTO']) && $this->inputArray['server']['HTTP_X_FORWARDED_PROTO'] === 'https')
return true;
elseif ( ! empty($this->inputArray['server']['HTTP_FRONT_END_HTTPS']) && strtolower($this->inputArray['server']['HTTP_FRONT_END_HTTPS']) !== 'off')
return true;
return false;
}
} }

View File

@ -38,9 +38,11 @@ namespace FuzeWorks;
use FuzeWorks\ConfigORM\ConfigORM; use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\Event\HelperLoadEvent;
use FuzeWorks\Exception\OutputException; use FuzeWorks\Exception\OutputException;
/**
* @todo Implement caching
*/
class Output class Output
{ {
@ -51,6 +53,13 @@ class Output
*/ */
private $input; private $input;
/**
* The internal URI class
*
* @var URI
*/
private $uri;
/** /**
* WebCfg * WebCfg
* *
@ -82,12 +91,38 @@ class Output
public $mimes = []; public $mimes = [];
protected $mimeType = 'text/html'; protected $mimeType = 'text/html';
/**
* The amount of time the current page is cached
*
* @var int $cacheTime
*/
protected $cacheTime = 0;
/**
* Whether a cache file is being used now
*
* @var bool
*/
protected $usingCache = false;
/**
* The status code that will be sent to the client
*
* @var int $statusCode
*/
protected $statusCode = 200; protected $statusCode = 200;
/**
* The status code text that will be sent along with $statusCode
*
* @var string $statusText
*/
protected $statusText = 'OK'; protected $statusText = 'OK';
public function init() public function init()
{ {
$this->input = Factory::getInstance()->input; $this->input = Factory::getInstance()->input;
$this->uri = Factory::getInstance()->uri;
$this->mimes = Factory::getInstance()->config->getConfig('mimes')->toArray(); $this->mimes = Factory::getInstance()->config->getConfig('mimes')->toArray();
$this->config = Factory::getInstance()->config->getConfig('web'); $this->config = Factory::getInstance()->config->getConfig('web');
@ -95,6 +130,200 @@ class Output
$this->compressOutput = (!$zlib && $this->config->get('compress_output') && extension_loaded('zlib')); $this->compressOutput = (!$zlib && $this->config->get('compress_output') && extension_loaded('zlib'));
} }
/**
* Display Output
*
* Processes and sends finalized output data to the browser along
* with any server headers.
*
* @param string $output Output data override
* @return void
*/
public function display(string $output = null)
{
// Set the output data
$output = is_null($output) ? $this->output : $output;
// Write cache if requested to do so
if ($this->cacheTime > 0)
$this->writeCache($output);
// First send status code
http_response_code($this->statusCode);
@header('Status: ' . $this->statusCode . ' ' . $this->statusText, true);
// If compression is requested, start buffering
if (
$this->compressOutput && !$this->usingCache &&
!is_null($this->input->server('HTTP_ACCEPT_ENCODING')) &&
strpos($this->input->server('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
)
{
Logger::log("Compressing output...");
ob_start('ob_gzhandler');
}
// Send gzip headers when using cache
if ($this->usingCache && $this->compressOutput)
{
if (!is_null($this->input->server('HTTP_ACCEPT_ENCODING')) &&
strpos($this->input->server('HTTP_ACCEPT_ENCODING'), 'gzip') !== false)
{
header('Content-Encoding: gzip');
header('Content-Length: '.strlen($output));
}
// If the cache is zipped, but the client doesn't support it, decompress the output
else
$output = gzinflate(substr($output, 10, -8));
}
// Send all available headers
if (!empty($this->headers))
foreach ($this->headers as $header)
@header($header[0], $header[1]);
echo $output;
Logger::log('Output sent to browser');
}
/**
* Enable the current page to be cached
*
* Set the amount of time with the $time parameter.
*
* @param int $time In minutes
*/
public function cache(int $time)
{
$this->cacheTime = $time > 0 ? $time : 0;
}
public function getCache(string $selector): bool
{
// If empty, index page is requested
$selector = empty($selector) ? 'index' : $selector;
// Generate the full uri
$uri = $this->config->get('base_url') . $selector;
// Determine the file that holds the cache
if ($this->compressOutput)
$file = Core::$tempDir . DS . 'OutputCache' . DS . md5($uri) . '_gzip.fwcache';
else
$file = Core::$tempDir . DS . 'OutputCache' . DS . md5($uri) . '.fwcache';
// Determine if file exists
if (!file_exists($file))
return false;
// Retrieve cache
$cache = file_get_contents($file);
// Verify that this is a cache file
if (!preg_match('/^(.*)EndFuzeWorksCache--->/', $cache, $match))
return false;
// Retrieve data from cache file
$cacheInfo = unserialize($match[1]);
// Test if the cache has expired
if (time() > $cacheInfo['expire'])
{
// If not writeable, log warning and do not remove
if (!Core::isReallyWritable($file))
{
Logger::logWarning("Found expired output cache. Could not remove!");
return false;
}
// Delete file if expired
@unlink($file);
Logger::logInfo("Found expired output cache. Removed.");
return false;
}
// @todo Send cache header
// Send all the headers cached in the file
foreach ($cacheInfo['headers'] as $header)
$this->setHeader($header[0], $header[1]);
// And save the output
$this->usingCache = true;
$this->setOutput(substr($cache, strlen($match[0])));
Logger::logInfo("Found output cache. Set output.");
return true;
}
public function writeCache(string $output)
{
// First create cache directory
$cachePath = Core::$tempDir . DS . 'OutputCache';
// Attempt to create the OutputCache directory in the TempDirectory
if (!is_dir($cachePath) && !mkdir($cachePath, 0777, false))
{
Logger::logError("Could not write output cache. Cannot create directory. Are permissions set correctly?");
return false;
}
// If directory is not writable, return error
if (!Core::isReallyWritable($cachePath))
{
Logger::logError("Could not write output cache. No file permissions. Are permissions set correctly?");
return false;
}
// Generate the full uri
$uri = $this->config->get('base_url') . (empty($this->uri->uriString()) ? 'index' : $this->uri->uriString());
// Determine the file that holds the cache
if ($this->compressOutput)
$file = $cachePath . DS . md5($uri) . '_gzip.fwcache';
else
$file = $cachePath . DS . md5($uri) . '.fwcache';
// If compression is enabled, compress the output
if ($this->compressOutput)
{
$output = gzencode($output);
if ($this->getHeader('content-type') === null)
$this->setContentType($this->mimeType);
}
// Calculate expiry time
$expire = time() + ($this->cacheTime * 60);
// Prepare the cache contents
$cache = [
'expire' => $expire,
'headers' => $this->headers
];
// Create cache file contents
$cache = serialize($cache) . 'EndFuzeWorksCache--->' . $output;
// Write the cache
if (file_put_contents($file, $cache, LOCK_EX) === false)
{
@unlink($file);
Logger::logError("Could not write output cache. File error. Deleting cache file.");
return false;
}
// Lowering permissions to read only
chmod($cachePath, 0640);
// And report back
Logger::logInfo("Output cache has been saved.");
// @todo Set cache header
return true;
}
/** /**
* Get Output * Get Output
* *
@ -132,45 +361,6 @@ class Output
$this->output .= $output; $this->output .= $output;
} }
/**
* Display Output
*
* Processes and sends finalized output data to the browser along
* with any server headers.
*
* @param string $output Output data override
* @return void
*/
public function display(string $output = null)
{
// Set the output data
$output = is_null($output) ? $this->output : $output;
// First send status code
http_response_code($this->statusCode);
@header('Status: ' . $this->statusCode . ' ' . $this->statusText, true);
// If compression is requested, start buffering
if (
$this->compressOutput &&
!is_null($this->input->server('HTTP_ACCEPT_ENCODING')) &&
strpos($this->input->server('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
)
{
Logger::log("Compressing output...");
ob_start('ob_gzhandler');
}
// Send all available headers
if (!empty($this->headers))
foreach ($this->headers as $header)
@header($header[0], $header[1]);
echo $output;
Logger::log('Output sent to browser');
}
/** /**
* Set Header * Set Header
* *
@ -181,6 +371,11 @@ class Output
*/ */
public function setHeader(string $header, bool $replace = true) public function setHeader(string $header, bool $replace = true)
{ {
// If compression is enabled content-length should be suppressed, since it won't match the length
// of the compressed output.
if ($this->compressOutput && strncasecmp($header, 'content-length', 14) === 0)
return;
$this->headers[] = [$header, $replace]; $this->headers[] = [$header, $replace];
} }
@ -214,7 +409,6 @@ class Output
* @param string $mimeType Extension of the file we're outputting * @param string $mimeType Extension of the file we're outputting
* @param string $charset Character set (default: NULL) * @param string $charset Character set (default: NULL)
*/ */
public function setContentType(string $mimeType, $charset = null) public function setContentType(string $mimeType, $charset = null)
{ {
if (strpos($mimeType, '/') === false) if (strpos($mimeType, '/') === false)

177
src/FuzeWorks/Resources.php Normal file
View File

@ -0,0 +1,177 @@
<?php
/**
* FuzeWorks WebComponent.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2019 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
namespace FuzeWorks;
use FuzeWorks\Event\ResourceServeEvent;
use FuzeWorks\Exception\WebException;
/**
* FuzeWorks' handler for static resources.
*
* Objects in FuzeWorks can register a folder with static resources, which shall be served if requested by clients.
* This system should be avoided for high-performance applications. It is recommended to make special configurations in the web server
* in those kinds of cases.
*/
class Resources
{
private $resources = [];
/**
* @var Output
*/
private $output;
public function init()
{
$this->output = Factory::getInstance()->output;
}
public function resourceExists(array $resourceUrlSegments): bool
{
// First find the resource
$file = $this->findResource($resourceUrlSegments);
// If not found, return false;
if (is_null($file))
return false;
// If found, simply return true
return true;
}
/**
* Serves a static file if found.
*
* @param array $resourceUrlSegments
* @return bool
* @throws WebException
*
* @todo Bypass the Output system and use the readFile() method.
* @todo Run as FuzeWorks pre-code, before creating the container
*/
public function serveResource(array $resourceUrlSegments): bool
{
// First find the resource
$file = $this->findResource($resourceUrlSegments);
// If not found return false
if (is_null($file))
return false;
// If a file is found, fire a serveResourceEvent
/** @var ResourceServeEvent $event */
try {
$event = Events::fireEvent('resourceServeEvent', $file['resourceName'], $file['segments'], $file['file']);
} catch (Exception\EventException $e) {
throw new WebException("Could not serve resource. resourceServeEvent threw exception: '" . $e->getMessage() . "'");
}
// If cancelled, don't serve
if ($event->isCancelled())
return false;
// Log the resource serving
Logger::log("Serving static resource '/" . $file['resourceName'] . '/' . implode('/', $file['segments']) . "'");
// Serve file in accordance with event
$fileExtension = pathinfo($event->resourceFilePath, PATHINFO_EXTENSION);
$this->output->setContentType($fileExtension);
$this->output->setOutput(file_get_contents($event->resourceFilePath));
#readfile($event->resourceFilePath);
// And return true at the end
return true;
}
protected function findResource(array $resourceUrlSegments): ?array
{
// If too few segments provided, don't even bother
if (count($resourceUrlSegments) < 2)
return null;
// First segment should be the resourceName, check if it exists
$resourceName = urldecode($resourceUrlSegments[1]);
if (!isset($this->resources[$resourceName]))
return null;
// If resource is found, generate file path
$resourceUrlSegmentsBck = $resourceUrlSegments;
array_shift($resourceUrlSegments);
$file = $this->resources[$resourceName] . DS . implode(DS, $resourceUrlSegments);
// Test if file exists, if it does, return the string
if (file_exists($file) && is_file($file))
return ['file' => $file, 'resourceName' => $resourceName, 'segments' => $resourceUrlSegments];
return null;
}
/**
* Register a resource which can be served statically.
*
* The resourceName will be the directory under which the files shall be served on the web server.
* The filePath is where FuzeWorks should look for the files.
*
* @param string $resourceName
* @param string $filePath
* @throws WebException
* @return bool
*/
public function registerResources(string $resourceName, string $filePath): bool
{
// First check if the resource already exists
$resourceName = urldecode($resourceName);
if (isset($this->resources[$resourceName]))
throw new WebException("Could not register resources. Resources with same name already exists.");
// Also check if the file path exists and is a directory
if (!file_exists($filePath) && !is_dir($filePath))
throw new WebException("Could not register resources. Provided filePath does not exist.");
// Add the resource
$this->resources[$resourceName] = $filePath;
// Log the registration
Logger::log("Adding static resources on: '/" . $resourceName . "'");
return true;
}
}

View File

@ -37,7 +37,7 @@
namespace FuzeWorks; namespace FuzeWorks;
use FuzeWorks\ConfigORM\ConfigORM; use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\Exception\{ConfigException, SecurityException, Exception}; use FuzeWorks\Exception\{ConfigException, CSRFException, Exception};
/** /**
* Security Class * Security Class
@ -45,6 +45,7 @@ use FuzeWorks\Exception\{ConfigException, SecurityException, Exception};
* @author EllisLab Dev Team * @author EllisLab Dev Team
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
* @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) * @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/)
* @todo Complete rewrite
*/ */
class Security { class Security {
@ -168,6 +169,13 @@ class Security {
*/ */
private $config; private $config;
/**
* Input. A dependency for this class
*
* @var Input
*/
private $input;
/** /**
* Class constructor * Class constructor
* *
@ -177,6 +185,7 @@ class Security {
public function init() public function init()
{ {
$this->config = Factory::getInstance()->config->get('security'); $this->config = Factory::getInstance()->config->get('security');
$this->input = Factory::getInstance()->input;
// Is CSRF protection enabled? // Is CSRF protection enabled?
if ($this->config->csrf_protection) if ($this->config->csrf_protection)
@ -200,7 +209,7 @@ class Security {
$this->_csrf_set_hash(); $this->_csrf_set_hash();
} }
$this->charset = strtoupper(Factory::getInstance()->config->get('web')->charset); $this->charset = strtoupper(Factory::getInstance()->config->get('web')->get('charset'));
} }
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -209,14 +218,13 @@ class Security {
* CSRF Verify * CSRF Verify
* *
* @return self * @return self
* @throws CSRFException
*/ */
public function csrf_verify(): self public function csrf_verify(): self
{ {
// If it's not a POST request we will set the CSRF cookie // If it's not a POST request we will set the CSRF cookie
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') if (strtoupper($this->input->server('REQUEST_METHOD')) !== 'POST')
{
return $this->csrf_set_cookie(); return $this->csrf_set_cookie();
}
// Check if URI has been whitelisted from CSRF checks // Check if URI has been whitelisted from CSRF checks
if ($exclude_uris = $this->config->csrf_exclude_uris) if ($exclude_uris = $this->config->csrf_exclude_uris)
@ -231,22 +239,10 @@ class Security {
} }
// Do the tokens exist in both the _POST and _COOKIE arrays? // Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]) $token = $this->input->post($this->_csrf_token_name);
OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match? $cookie = $this->input->cookie($this->_csrf_cookie_name);
{ if ($token == null || $cookie == null || $token !== $cookie)
$this->csrf_show_error(); $this->csrf_show_error();
}
// We kill this since we're done and we don't want to polute the _POST array
unset($_POST[$this->_csrf_token_name]);
// Regenerate on every submission?
if ($this->config->csrf_regenerate)
{
// Nothing should last forever
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_hash = NULL;
}
$this->_csrf_set_hash(); $this->_csrf_set_hash();
$this->csrf_set_cookie(); $this->csrf_set_cookie();
@ -257,6 +253,22 @@ class Security {
// -------------------------------------------------------------------- // --------------------------------------------------------------------
/**
* CSRF Regenerate
*
* @throws ConfigException
* @return self
*/
public function csrf_regenerate()
{
Logger::log("CSRF Hash is being regenerated...");
$this->_csrf_hash = null;
$this->_csrf_set_hash();
$this->csrf_set_cookie();
return $this;
}
/** /**
* CSRF Set Cookie * CSRF Set Cookie
* *
@ -266,24 +278,25 @@ class Security {
public function csrf_set_cookie() public function csrf_set_cookie()
{ {
$expire = time() + $this->_csrf_expire; $expire = time() + $this->_csrf_expire;
$cfg = Factory::getInstance()->config->get('main'); $cfg = Factory::getInstance()->config->get('security');
$secure_cookie = (bool) $cfg->cookie_secure; $secure_cookie = (bool) $cfg->csrf_cookie_secure;
if ($secure_cookie && ! Core::isHttps()) if ($secure_cookie && !$this->input->isHttps())
{
return $this; return $this;
}
setcookie( setcookie(
$this->_csrf_cookie_name, $this->_csrf_cookie_name,
$this->_csrf_hash, $this->_csrf_hash,
$expire, $expire,
$cfg->cookie_path, $cfg->csrf_cookie_path,
$cfg->cookie_domain, $cfg->csrf_cookie_domain,
$secure_cookie, $secure_cookie,
$cfg->cookie_httponly $cfg->csrf_cookie_httponly
); );
Logger::log('CSRF cookie sent'); Logger::log('CSRF cookie sent');
return $this;
} }
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -292,11 +305,11 @@ class Security {
* Show CSRF Error * Show CSRF Error
* *
* @return void * @return void
* @throws SecurityException * @throws CSRFException
*/ */
public function csrf_show_error() public function csrf_show_error()
{ {
throw new SecurityException('The action you have requested is not allowed.', 1); throw new CSRFException('This action resulted in a Cross Site Reference Forgery warning. Request will be blocked...', 5);
} }
// -------------------------------------------------------------------- // --------------------------------------------------------------------
@ -727,57 +740,6 @@ class Security {
return $str; return $str;
} }
// --------------------------------------------------------------------
/**
* Sanitize Filename
*
* @param string $str Input file name
* @param bool $relative_path Whether to preserve paths
* @return string
*/
public function sanitize_filename($str, $relative_path = FALSE): string
{
$bad = $this->filename_bad_chars;
if ( ! $relative_path)
{
$bad[] = './';
$bad[] = '/';
}
$str = UTF8::removeInvisibleCharacters($str, FALSE);
do
{
$old = $str;
$str = str_replace($bad, '', $str);
}
while ($old !== $str);
return stripslashes($str);
}
// ----------------------------------------------------------------
/**
* Strip Image Tags
*
* @param string $str
* @return string
*/
public function strip_image_tags($str): string
{
return preg_replace(
array(
'#<img[\s/]+.*?src\s*=\s*(["\'])([^\\1]+?)\\1.*?\>#i',
'#<img[\s/]+.*?src\s*=\s*?(([^\s"\'=<>`]+)).*?\>#i'
),
'\\2',
$str
);
}
// ---------------------------------------------------------------- // ----------------------------------------------------------------
/** /**

View File

@ -60,17 +60,15 @@ class URI
private $input; private $input;
protected $baseUri; protected $baseUri;
protected $uriString; protected $uriString = '';
protected $segments; protected $segments = [];
public function init() public function init()
{ {
$this->input = Factory::getInstance()->input; $this->input = Factory::getInstance()->input;
$this->config = Factory::getInstance()->config->getConfig('web'); $this->config = Factory::getInstance()->config->getConfig('web');
if (WebComponent::$willHandleRequest) if (WebComponent::$willHandleRequest)
{
$this->determineUri(); $this->determineUri();
}
} }
public function determineUri() public function determineUri()
@ -86,7 +84,7 @@ class URI
$scriptName = $this->input->server('SCRIPT_NAME'); $scriptName = $this->input->server('SCRIPT_NAME');
$scriptFilename = $this->input->server('SCRIPT_FILENAME'); $scriptFilename = $this->input->server('SCRIPT_FILENAME');
$baseUrl = ($this->isHttps() ? 'https' : 'http') . $baseUrl = ($this->input->isHttps() ? 'https' : 'http') .
"://" . $serverAddr . "://" . $serverAddr .
substr($scriptName, 0, strpos($scriptName, basename($scriptFilename))); substr($scriptName, 0, strpos($scriptName, basename($scriptFilename)));
} }
@ -224,31 +222,4 @@ class URI
return true; return true;
} }
/**
* Is HTTPS?
*
* Determines if the application is accessed via an encrypted
* (HTTPS) connection.
*
* @return bool
*/
protected function isHttps(): bool
{
$https = $this->input->server('HTTPS');
if (!is_null($https) && strtolower($https) !== 'off')
{
return true;
}
elseif (!is_null($this->input->server('HTTP_X_FORWARDED_PROTO')) && $this->input->server('HTTP_X_FORWARDED_PROTO') === 'https')
{
return true;
}
elseif ( ! is_null($this->input->server('HTTP_FRONT_END_HTTPS')) && strtolower($this->input->server('HTTP_FRONT_END_HTTPS')) !== 'off')
{
return true;
}
return false;
}
} }

View File

@ -36,14 +36,24 @@
namespace FuzeWorks; namespace FuzeWorks;
use FuzeWorks\Event\HaltExecutionEvent;
use FuzeWorks\Event\LayoutLoadEvent;
use FuzeWorks\Event\RouterCallViewEvent;
use FuzeWorks\Event\RouterLoadViewAndControllerEvent;
use FuzeWorks\Event\RouteWebRequestEvent;
use FuzeWorks\Exception\ConfigException;
use FuzeWorks\Exception\EventException; use FuzeWorks\Exception\EventException;
use FuzeWorks\Exception\Exception; use FuzeWorks\Exception\Exception;
use FuzeWorks\Exception\FactoryException;
use FuzeWorks\Exception\HaltException;
use FuzeWorks\Exception\NotFoundException; use FuzeWorks\Exception\NotFoundException;
use FuzeWorks\Exception\OutputException;
use FuzeWorks\Exception\RouterException;
use FuzeWorks\Exception\SecurityException;
use FuzeWorks\Exception\WebException; use FuzeWorks\Exception\WebException;
class WebComponent implements iComponent class WebComponent implements iComponent
{ {
/** /**
* Whether WebComponent is configured to handle a web request * Whether WebComponent is configured to handle a web request
* *
@ -64,13 +74,10 @@ class WebComponent implements iComponent
'input' => '\FuzeWorks\Input', 'input' => '\FuzeWorks\Input',
'output' => '\FuzeWorks\Output', 'output' => '\FuzeWorks\Output',
'uri' => '\FuzeWorks\URI', 'uri' => '\FuzeWorks\URI',
'resources' => '\FuzeWorks\Resources'
]; ];
} }
/**
* @param Configurator $configurator
* @todo WebComponent will not always be running when added to FuzeWorks, move this into a separate method
*/
public function onAddComponent(Configurator $configurator) public function onAddComponent(Configurator $configurator)
{ {
// Add dependencies // Add dependencies
@ -95,60 +102,133 @@ class WebComponent implements iComponent
{ {
} }
/**
* On initializing, Initialize UTF8 first, since it's a dependency for other componentClasses
*/
public function init() public function init()
{ {
// First init UTF8 // First init UTF8
UTF8::init(); UTF8::init();
// Register some base events
Events::addListener([$this, 'layoutLoadEventListener'], 'layoutLoadEvent', Priority::NORMAL);
} }
/**
* Enable the WebComponent to prepare for handling requests
*/
public function enableComponent() public function enableComponent()
{ {
self::$willHandleRequest = true; self::$willHandleRequest = true;
} }
/**
* Disable the WebComponent so it won't prepare for handling requests
*/
public function disableComponent() public function disableComponent()
{ {
self::$willHandleRequest = false; self::$willHandleRequest = false;
} }
/** /**
* Handle a Web request.
*
* Retrieves URI string, routes this URI using the provided routes,
* appends output and adds listener to view output on shutdown.
*
* @return bool
* @throws OutputException
* @throws RouterException
* @throws WebException * @throws WebException
* @throws EventException
* @throws FactoryException
*/ */
public function routeWebRequest(): bool public function routeWebRequest(): bool
{ {
if (!self::$willHandleRequest) if (!self::$willHandleRequest)
throw new WebException("Could not route web request. WebComponent is not configured to handle requests"); throw new WebException("Could not route web request. WebComponent is not configured to handle requests");
// Set the output to display when shutting down
try { try {
Events::addListener(function () { // Set the output to display when shutting down
Events::addListener(function ($event) {
/** @var Output $output */ /** @var Output $output */
Logger::logInfo("Parsing output..."); Logger::logInfo("Parsing output...");
$output = Factory::getInstance()->output; $output = Factory::getInstance()->output;
$output->display(); $output->display();
return $event;
}, 'coreShutdownEvent', Priority::NORMAL); }, 'coreShutdownEvent', Priority::NORMAL);
// Add HTTP method prefix to requests to views
Events::addListener(function($event){
/** @var Input $input */
/** @var RouterLoadViewAndControllerEvent $event */
$input = Factory::getInstance('input');
$methods = $event->viewMethods[Priority::NORMAL];
foreach ($methods as $method)
$event->addMethod(strtolower($input->method()) . '_' . $method);
return $event;
}, 'routerLoadViewAndControllerEvent', Priority::NORMAL);
// Create an error 500 page when a haltEvent is fired
Events::addListener([$this, 'haltEventListener'], 'haltExecutionEvent', Priority::NORMAL);
} catch (EventException $e) { } catch (EventException $e) {
throw new WebException("Could not route web request. coreShutdownEvent threw EventException: '".$e->getMessage()."'"); throw new WebException("Could not route web request. coreShutdownEvent threw EventException: '".$e->getMessage()."'");
} }
// Remove the X-Powered-By header, since it's a security risk
header_remove("X-Powered-By");
/** @var Router $router */ /** @var Router $router */
$router = Factory::getInstance()->router; /** @var URI $uri */
/** @var URI $uriObject */
$uriObject = Factory::getInstance()->uri;
$uri = $uriObject->uriString();
/** @var Output $output */ /** @var Output $output */
$output = Factory::getInstance()->output; /** @var Security $security */
/** @var Resources $resources */
$router = Factory::getInstance('router');
$uri = Factory::getInstance('uri');
$output = Factory::getInstance('output');
$security = Factory::getInstance('security');
$resources = Factory::getInstance('resources');
// And start logging the request
Logger::newLevel("Routing web request...");
// First check if a cached page is available
$uriString = $uri->uriString();
if ($output->getCache($uriString))
return true;
// Send webRequestEvent, if no cache is found
/** @var RouteWebRequestEvent $event */
$event = Events::fireEvent('routeWebRequestEvent', $uriString);
if ($event->isCancelled())
return true;
// Attempt to load a static resource
if ($resources->serveResource($uri->segmentArray()))
return true;
// First test for Cross Site Request Forgery
try {
$security->csrf_verify();
} catch (SecurityException $exception) {
// If a SecurityException is thrown, first log it
Logger::logWarning("SecurityException thrown. Registering listener to verify handler in View");
// Register a listener
Events::addListener([$this, 'callViewEventListener'], 'routerCallViewEvent', Priority::HIGHEST, $exception);
}
// Attempt to load the requested page // Attempt to load the requested page
try { try {
$viewOutput = $router->route($uri); $viewOutput = $router->route($event->uriString);
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
Logger::logWarning("Requested page not found. Requesting Error/error404 View"); Logger::logWarning("Requested page not found. Requesting Error/error404 View");
$output->setStatusHeader(404); $output->setStatusHeader(404);
// Request 404 page= // Remove listener so that error pages won't be intercepted
Events::removeListener([$this, 'callViewEventListener'], 'routerCallViewEvent',Priority::HIGHEST);
// Request 404 page
try { try {
$viewOutput = $router->route('Error/error404'); $viewOutput = $router->route('Error/error404');
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
@ -158,14 +238,21 @@ class WebComponent implements iComponent
Logger::exceptionHandler($e, false); Logger::exceptionHandler($e, false);
$viewOutput = 'ERROR 404. Page was not found.'; $viewOutput = 'ERROR 404. Page was not found.';
} }
} catch (Exception $e) { } catch (HaltException $e) {
Logger::exceptionHandler($e, false); Logger::logWarning("Requested page was denied. Requesting Error/error403 View.");
$output->setStatusHeader(500); $output->setStatusHeader(403);
// Remove listener so that error pages won't be intercepted
Events::removeListener([$this, 'callViewEventListener'], 'routerCallViewEvent',Priority::HIGHEST);
try { try {
$viewOutput = $router->route('Error/error500'); $viewOutput = $router->route('Error/error403');
} catch (Exception $error500Exception) { } catch (NotFoundException $e) {
Logger::exceptionHandler($error500Exception, false); // If still resulting in an error, do something else
$viewOutput = 'ERROR 500. Page could not be loaded.'; $viewOutput = 'ERROR 403. Forbidden.';
} catch (Exception $e) {
Logger::exceptionHandler($e, false);
$viewOutput = 'ERROR 403. Forbidden.';
} }
} }
@ -173,6 +260,99 @@ class WebComponent implements iComponent
if (!empty($viewOutput)) if (!empty($viewOutput))
$output->appendOutput($viewOutput); $output->appendOutput($viewOutput);
Logger::stopLevel();
return true; return true;
} }
/**
* Listener for routerCallViewEvent
*
* Fired when a SecurityException is thrown. Verifies if a securityExceptionHandler() method exists.
* If not, the calling of the view is cancelled. If yes, the calling of the view depends on the
* result of the method
*
* @param RouterCallViewEvent $event
* @param SecurityException $exception
*/
public function callViewEventListener(RouterCallViewEvent $event, SecurityException $exception)
{
/** @var RouterCallViewEvent $event */
// If the securityExceptionHandler method exists, cancel based on that methods output
if (method_exists($event->view, 'securityExceptionHandler'))
$event->setCancelled(!$event->view->securityExceptionHandler($exception));
// If not, cancel it immediately
else
$event->setCancelled(true);
}
/**
* Listener for haltExecutionEvent
*
* Fired when FuzeWorks halts it's execution. Loads an error 500 page.
*
* @param $event
* @throws EventException
* @throws FactoryException
* @TODO remove FuzeWorks\Layout dependency
*/
public function haltEventListener(HaltExecutionEvent $event)
{
// Dependencies
/** @var Output $output */
/** @var Router $router */
/** @var Event $event */
/** @var Layout $layout */
$output = Factory::getInstance()->output;
$router = Factory::getInstance()->router;
$layout = Factory::getInstance()->layouts;
// Cancel event
$event->setCancelled(true);
// Reset the layout engine
$layout->reset();
// Remove listener so that error pages won't be intercepted
Events::removeListener([$this, 'callViewEventListener'], 'routerCallViewEvent',Priority::HIGHEST);
try {
// And handle consequences
Logger::logError("Execution halted. Providing error 500 page.");
$output->setStatusHeader(500);
$viewOutput = $router->route('Error/error500');
} catch (Exception $error500Exception) {
Logger::exceptionHandler($error500Exception, false);
$viewOutput = 'ERROR 500. Page could not be loaded.';
}
// Finally append output and shutdown
$output->appendOutput($viewOutput);
}
/**
* Listener for layoutLoadEvent
*
* Assigns variables from the WebComponent to Layout engines.
*
* @param LayoutLoadEvent $event
* @throws ConfigException
* @throws FactoryException
*/
public function layoutLoadEventListener($event)
{
// Dependencies
/** @var Security $security */
/** @var Config $config */
$security = Factory::getInstance()->security;
$config = Factory::getInstance()->config;
/** @var LayoutLoadEvent $event */
$event->assign('csrfHash', $security->get_csrf_hash());
$event->assign('csrfTokenName', $security->get_csrf_token_name());
$event->assign('siteURL', $config->getConfig('web')->get('base_url'));
$event->assign('serverName', $config->getConfig('web')->get('serverName'));
Logger::logInfo("Assigned variables to TemplateEngine from WebComponent");
}
} }

View File

@ -36,33 +36,6 @@
namespace FuzeWorks; namespace FuzeWorks;
class WebController extends Controller abstract class WebController extends Controller
{ {
/**
* @var Input
*/
protected $input;
/**
* @var Output
*/
protected $output;
/**
* @var URI
*/
protected $uri;
/**
* WebView constructor.
*/
public function __construct()
{
parent::__construct();
$this->input = Factory::getInstance()->input;
$this->output = Factory::getInstance()->output;
$this->uri = Factory::getInstance()->uri;
}
} }

View File

@ -36,31 +36,6 @@
namespace FuzeWorks; namespace FuzeWorks;
class WebModel extends Model abstract class WebModel extends Model
{ {
/**
* @var Input
*/
protected $input;
/**
* @var Output
*/
protected $output;
/**
* @var URI
*/
protected $uri;
/**
* WebView constructor.
*/
public function __construct()
{
parent::__construct();
$this->input = Factory::getInstance()->input;
$this->output = Factory::getInstance()->output;
$this->uri = Factory::getInstance()->uri;
}
} }

View File

@ -36,7 +36,7 @@
namespace FuzeWorks; namespace FuzeWorks;
class WebView extends View abstract class WebView extends View
{ {
/** /**
* @var Input * @var Input
@ -53,6 +53,11 @@ class WebView extends View
*/ */
protected $uri; protected $uri;
/**
* @var Security
*/
protected $security;
/** /**
* WebView constructor. * WebView constructor.
*/ */
@ -62,5 +67,6 @@ class WebView extends View
$this->input = Factory::getInstance()->input; $this->input = Factory::getInstance()->input;
$this->output = Factory::getInstance()->output; $this->output = Factory::getInstance()->output;
$this->uri = Factory::getInstance()->uri; $this->uri = Factory::getInstance()->uri;
$this->security = Factory::getInstance()->security;
} }
} }