Compare commits

...

11 Commits

Author SHA1 Message Date
Abel Hoogeveen 58e885e35f Added the setLocation() method for Output.
Useful for redirecting the user to a different page and automatically setting the correct status header.
2023-02-14 14:55:46 +01:00
Abel Hoogeveen d6863d3f51 Merge pull request 'Fixed bug #3 which caused xss_clean() to fail when calling input variables from arrays.' (#5) from fix/3 into master
Reviewed-on: #5
2022-12-06 11:19:05 +00:00
Abel Hoogeveen 22e3ec2fd0 Fixed bug #3 which caused xss_clean() to fail when calling input variables from arrays.
Closes #3.
2022-12-06 12:18:13 +01:00
Abel Hoogeveen d66c244931
Merge branch 'master' of ssh://gitea.i15.nl:7070/FuzeWorks/WebComponent 2022-03-15 19:24:47 +01:00
Abel Hoogeveen 0e2eb5ef72
`config.web.php` already provides a prefix, so `config.security` should not provide one.
Can be merged later whenever necessary.

Also verifies if the protection is enabled or not.
2022-03-15 19:24:29 +01:00
Abel Hoogeveen 3c7011eddb
`config.web.php` already provides a prefix, so `config.security` should not provide one.
Can be merged later whenever necessary.
2022-03-15 19:18:45 +01:00
Abel Hoogeveen cd331dc39d
Stop lowering cache permissions.
- Temporary solution until ObjectStorage is implemented here.
2021-11-30 11:33:18 +01:00
Abel Hoogeveen 444f614c48
Updated compatibility of WebComponent.
- Now uses latest libraries of FuzeWorks.
2021-11-29 22:47:45 +01:00
Abel Hoogeveen af25072b24
Upgraded dependencies and upped the LICENSE. 2021-01-25 12:21:41 +01:00
Abel Hoogeveen d7b2c40c57
Fixed Resources being unable to serve static files when using more complicated URI's. 2020-08-02 11:12:51 +02:00
Abel Hoogeveen e3485fa256 Separated events into methods so they can be properly logged by Core. 2020-07-12 12:02:43 +02:00
9 changed files with 130 additions and 86 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2021 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.

View File

@ -4,21 +4,19 @@
"license": ["MIT"],
"authors": [
{
"name": "TechFuze",
"homepage": "https://techfuze.net"
},
{
"name": "FuzeWorks Community",
"homepage": "https://techfuze.net/fuzeworks/contributors"
"name": "Abel Hoogeveen",
"homepage": "https://i15.nl"
}
],
"require": {
"php": ">=7.1.0",
"fuzeworks/mvcr": "~1.2.0",
"fuzeworks/core": "~1.2.0"
"php": ">=8.1.0",
"fuzeworks/core": "~1.3.0",
"fuzeworks/mvcr": "~1.3.0",
"fuzeworks/objectstorage": "~1.3.0"
},
"require-dev": {
"phpunit/phpunit": "^7"
"fuzeworks/layout": "~1.3.0",
"fuzeworks/tracycomponent": "~1.3.0"
},
"autoload": {
"psr-4": {

View File

@ -50,12 +50,12 @@ return [
| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks
*/
'csrf_protection' => true,
'csrf_token_name' => 'fw_csrf_token',
'csrf_token_name' => 'csrf_token',
'csrf_expire' => 7200,
'csrf_exclude_uris' => array(),
// CSRF Cookie information
'csrf_cookie_name' => 'fw_csrf_cookie',
'csrf_cookie_name' => 'csrf_cookie',
'csrf_cookie_prefix' => '',
'csrf_cookie_domain' => '',
'csrf_cookie_path' => '/',

View File

@ -47,17 +47,17 @@ class ResourceServeEvent extends Event
/**
* @var array
*/
public $resourceUrlSegments;
public $requestURL;
/**
* @var string
*/
public $resourceFilePath;
public function init(string $resourceName, array $resourceUrlSegments, string $resourceFilePath)
public function init(string $resourceName, string $requestURL, string $resourceFilePath)
{
$this->resourceName = $resourceName;
$this->resourceUrlSegments = $resourceUrlSegments;
$this->requestURL = $requestURL;
$this->resourceFilePath = $resourceFilePath;
}
}

View File

@ -327,7 +327,6 @@ class Input
/**
* Fetch the HTTP_USER_AGENT variable from the $_SERVER array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/
@ -339,7 +338,6 @@ class Input
/**
* Fetch the REQUEST_METHOD variable from the $_SERVER array
*
* @param string|array|null $index
* @param bool $xssClean
* @return mixed
*/

View File

@ -289,13 +289,13 @@ class Output
$getParams = $this->input->get();
// Determine the identifier
$identier = md5($uri . '|' . serialize($getParams));
$identifier = md5($uri . '|' . serialize($getParams));
// Determine the file that holds the cache
if ($this->compressOutput)
$file = $cachePath . DS . $identier . '_gzip.fwcache';
$file = $cachePath . DS . $identifier . '_gzip.fwcache';
else
$file = $cachePath . DS . $identier . '.fwcache';
$file = $cachePath . DS . $identifier . '.fwcache';
// If compression is enabled, compress the output
@ -326,9 +326,6 @@ class Output
return false;
}
// Lowering permissions to read only
chmod($cachePath, 0640);
// And report back
Logger::logInfo("Output cache has been saved.");
@ -525,4 +522,25 @@ class Output
}
}
/**
* Set the location to redirect the user to.
*
* @param string $locationUrl Should be prepended with /
* @param bool $permanent True for 301, false for 302 redirect.
* @return void
*/
public function setLocation(string $locationUrl, bool $permanent = false)
{
// Set the status header
if ($permanent)
$this->setStatusHeader(301);
else
$this->setStatusHeader(302);
// And the location itself
$header = 'Location: ' . $locationUrl;
$this->headers[] = [$header, true];
}
}

View File

@ -61,10 +61,10 @@ class Resources
$this->output = Factory::getInstance()->output;
}
public function resourceExists(array $resourceUrlSegments): bool
public function resourceExists(string $requestURL): bool
{
// First find the resource
$file = $this->findResource($resourceUrlSegments);
$file = $this->findResource($requestURL);
// If not found, return false;
if (is_null($file))
@ -77,17 +77,17 @@ class Resources
/**
* Serves a static file if found.
*
* @param array $resourceUrlSegments
* @param string $requestURL
* @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
public function serveResource(string $requestURL): bool
{
// First find the resource
$file = $this->findResource($resourceUrlSegments);
$file = $this->findResource($requestURL);
// If not found return false
if (is_null($file))
@ -96,7 +96,7 @@ class Resources
// If a file is found, fire a serveResourceEvent
/** @var ResourceServeEvent $event */
try {
$event = Events::fireEvent('resourceServeEvent', $file['resourceName'], $file['segments'], $file['file']);
$event = Events::fireEvent('resourceServeEvent', $file['resourceName'], $file['requestURL'], $file['file']);
} catch (Exception\EventException $e) {
throw new WebException("Could not serve resource. resourceServeEvent threw exception: '" . $e->getMessage() . "'");
}
@ -106,7 +106,7 @@ class Resources
return false;
// Log the resource serving
Logger::log("Serving static resource '/" . $file['resourceName'] . '/' . implode('/', $file['segments']) . "'");
Logger::log("Serving static resource '/" . $file['resourceName'] . '/' . $file['requestURL'] . "'");
// Serve file in accordance with event
$fileExtension = pathinfo($event->resourceFilePath, PATHINFO_EXTENSION);
@ -118,25 +118,22 @@ class Resources
return true;
}
protected function findResource(array $resourceUrlSegments): ?array
protected function findResource(string $requestURL): ?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;
foreach ($this->resources as $resourceName => $resourceDir)
{
if (substr($requestURL, 0, strlen($resourceName)) === $resourceName)
{
$fileURL = ltrim(substr($requestURL, strlen($resourceName)), '/');
$fileURL = str_replace('/', DS, $fileURL);
$file = $this->resources[$resourceName] . DS . $fileURL;
// 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];
// Test if file exists, if it does, return the string
if (file_exists($file) && is_file($file))
return ['file' => $file, 'resourceName' => $resourceName, 'requestURL' => $fileURL];
}
}
return null;
}

View File

@ -188,7 +188,7 @@ class Security {
$this->input = Factory::getInstance()->input;
// Is CSRF protection enabled?
if ($this->config->csrf_protection)
if ($this->config->get('csrf_protection'))
{
// CSRF config
foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key)
@ -222,6 +222,10 @@ class Security {
*/
public function csrf_verify(): self
{
// If not enabled, do not run
if (!$this->config->get('csrf_protection'))
return $this;
// If it's not a POST request we will set the CSRF cookie
if (strtoupper($this->input->server('REQUEST_METHOD')) !== 'POST')
return $this->csrf_set_cookie();
@ -371,10 +375,8 @@ class Security {
// Is the string an array?
if (is_array($str))
{
while (list($key) = each($str))
{
$str[$key] = $this->xss_clean($str[$key]);
}
foreach ($str as $key => $value)
$str[$key] = $this->xss_clean($value);
return $str;
}

View File

@ -60,7 +60,7 @@ class WebComponent implements iComponent
*
* @var bool
*/
public static $willHandleRequest = false;
public static bool $willHandleRequest = false;
public function getName(): string
{
@ -93,10 +93,8 @@ class WebComponent implements iComponent
// If WebComponent will handle a request, add some calls to the configurator
if (self::$willHandleRequest)
{
// Invoke methods to prepare system for HTTP calls
$configurator->call('logger', 'setLoggerTemplate', null, 'logger_http');
}
}
public function onCreateContainer(Factory $container)
@ -124,13 +122,44 @@ class WebComponent implements iComponent
}
/**
* Disable the WebComponent so it won't prepare for handling requests
* Disable the WebComponent, so it won't prepare for handling requests
*/
public function disableComponent()
{
self::$willHandleRequest = false;
}
public function shutdownEventListener(Event $event): Event
{
/** @var Output $output */
Logger::logInfo("Parsing output...");
$output = Factory::getInstance()->output;
$output->display();
return $event;
}
public function layoutDisplayEventListener(Event $event)
{
/** @var $event LayoutDisplayEvent */
/** @var Output $output */
$output = Factory::getInstance('output');
$output->appendOutput($event->contents);
$event->setCancelled(true);
}
public function routerLoadViewAndControllerEventListener(Event $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);
Logger::log("Added input method '" . $input->method() . "' as a prefix to view methods.");
return $event;
}
/**
* Handle a Web request.
*
@ -151,33 +180,13 @@ class WebComponent implements iComponent
try {
// Set the output to display when shutting down
Events::addListener(function ($event) {
/** @var Output $output */
Logger::logInfo("Parsing output...");
$output = Factory::getInstance()->output;
$output->display();
return $event;
}, 'coreShutdownEvent', Priority::NORMAL);
Events::addListener([$this, 'shutdownEventListener'], 'coreShutdownEvent', Priority::NORMAL);
// Intercept output of Layout and redirect it to Output
Events::addListener(function($event){
/** @var $event LayoutDisplayEvent */
/** @var Output $output */
$output = Factory::getInstance('output');
$output->appendOutput($event->contents);
$event->setCancelled(true);
}, 'layoutDisplayEvent', Priority::NORMAL);
Events::addListener([$this, 'layoutDisplayEventListener'], 'layoutDisplayEvent', 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);
Events::addListener([$this, 'routerLoadViewAndControllerEventListener'], 'routerLoadViewAndControllerEvent', Priority::NORMAL);
// Create an error 500 page when a haltEvent is fired
Events::addListener([$this, 'haltEventListener'], 'haltExecutionEvent', Priority::NORMAL);
@ -231,7 +240,7 @@ class WebComponent implements iComponent
return true;
// Attempt to load a static resource
if ($resources->serveResource($uri->segmentArray()))
if ($resources->serveResource($uri->uriString()))
return true;
// First test for Cross Site Request Forgery
@ -303,7 +312,6 @@ class WebComponent implements iComponent
*/
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));
@ -318,7 +326,7 @@ class WebComponent implements iComponent
*
* Fired when FuzeWorks halts it's execution. Loads an error 500 page.
*
* @param $event
* @param HaltExecutionEvent $event
* @throws EventException
* @throws FactoryException
* @TODO remove FuzeWorks\Layout dependency
@ -332,14 +340,17 @@ class WebComponent implements iComponent
/** @var Layout $layout */
$output = Factory::getInstance()->output;
$router = Factory::getInstance()->router;
$layout = Factory::getInstance()->layouts;
// Reset the layout engine
if (isset(Factory::getInstance()->layouts))
{
$layout = Factory::getInstance()->layouts;
$layout->reset();
}
// 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);
@ -374,7 +385,6 @@ class WebComponent implements iComponent
$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'));