Skip to content

Commit

Permalink
Merge pull request #8053 from kenjis/feat-required-filters
Browse files Browse the repository at this point in the history
feat: new Required Filters
  • Loading branch information
kenjis authored Nov 19, 2023
2 parents 4907a18 + 709b9f2 commit b5451cb
Show file tree
Hide file tree
Showing 21 changed files with 841 additions and 182 deletions.
2 changes: 1 addition & 1 deletion app/Config/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class App extends BaseConfig
* If true, this will force every request made to this application to be
* made via a secure connection (HTTPS). If the incoming request is not
* secure, the user will be redirected to a secure version of the page
* and the HTTP Strict Transport Security header will be set.
* and the HTTP Strict Transport Security (HSTS) header will be set.
*/
public bool $forceGlobalSecureRequests = false;

Expand Down
42 changes: 37 additions & 5 deletions app/Config/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,61 @@

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Config\Filters as BaseFilters;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\ForceHTTPS;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\PageCache;
use CodeIgniter\Filters\PerformanceMetrics;
use CodeIgniter\Filters\SecureHeaders;

class Filters extends BaseConfig
class Filters extends BaseFilters
{
/**
* Configures aliases for Filter classes to
* make reading things nicer and simpler.
*
* @var array<string, class-string|list<class-string>> [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
* @var array<string, class-string|list<class-string>>
*
* [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
*/
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'forcehttps' => ForceHTTPS::class,
'pagecache' => PageCache::class,
'performance' => PerformanceMetrics::class,
];

/**
* List of special required filters.
*
* The filters listed here are special. They are applied before and after
* other kinds of filters, and always applied even if a route does not exist.
*
* Filters set by default provide framework functionality. If removed,
* those functions will no longer work.
*
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
*
* @var array{before: list<string>, after: list<string>}
*/
public array $required = [
'before' => [
'forcehttps', // Force Global Secure Requests
'pagecache', // Web Page Caching
],
'after' => [
'pagecache', // Web Page Caching
'performance', // Performance Metrics
'toolbar', // Debug Toolbar
],
];

/**
Expand All @@ -39,7 +72,6 @@ class Filters extends BaseConfig
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
Expand Down
2 changes: 1 addition & 1 deletion phpstan-baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -1923,7 +1923,7 @@
];
$ignoreErrors[] = [
'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#',
'count' => 2,
'count' => 1,
'path' => __DIR__ . '/system/Filters/Filters.php',
];
$ignoreErrors[] = [
Expand Down
120 changes: 76 additions & 44 deletions system/CodeIgniter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Filters\Filters;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\Exceptions\RedirectException;
Expand Down Expand Up @@ -339,32 +340,83 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon
$this->getRequestObject();
$this->getResponseObject();

try {
$this->forceSecureAccess();
Events::trigger('pre_system');

$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}
$this->benchmark->stop('bootstrap');

$this->benchmark->start('required_before_filters');
// Start up the filters
$filters = Services::filters();
// Run required before filters
$possibleResponse = $this->runRequiredBeforeFilters($filters);

$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
$this->response = $this->display404errors($e);
} catch (Throwable $e) {
$this->outputBufferingEnd();
// If a ResponseInterface instance is returned then send it back to the client and stop
if ($possibleResponse instanceof ResponseInterface) {
$this->response = $possibleResponse;
} else {
try {
$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}

throw $e;
$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
$this->response = $this->display404errors($e);
} catch (Throwable $e) {
$this->outputBufferingEnd();

throw $e;
}
}

$this->runRequiredAfterFilters($filters);

// Is there a post-system event?
Events::trigger('post_system');

if ($returnResponse) {
return $this->response;
}

$this->sendResponse();
}

/**
* Run required before filters.
*/
private function runRequiredBeforeFilters(Filters $filters): ?ResponseInterface
{
$possibleResponse = $filters->runRequired('before');
$this->benchmark->stop('required_before_filters');

// If a ResponseInterface instance is returned then send it back to the client and stop
if ($possibleResponse instanceof ResponseInterface) {
return $possibleResponse;
}

return null;
}

/**
* Run required after filters.
*/
private function runRequiredAfterFilters(Filters $filters): void
{
$filters->setResponse($this->response);

// Run required after filters
$this->benchmark->start('required_after_filters');
$response = $filters->runRequired('after');
$this->benchmark->stop('required_after_filters');

if ($response instanceof ResponseInterface) {
$this->response = $response;
}
}

/**
* Invoked via php-cli command?
*/
Expand Down Expand Up @@ -405,20 +457,11 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
}

Events::trigger('pre_system');

// Check for a cached page. Execution will stop
// if the page has been cached.
if (($response = $this->displayCache($cacheConfig)) instanceof ResponseInterface) {
return $response;
}

$routeFilters = $this->tryToRouteIt($routes);

$uri = $this->request->getPath();

if ($this->enableFilters) {
// Start up the filters
$filters = Services::filters();

// If any filters were specified within the routes file,
Expand Down Expand Up @@ -478,9 +521,6 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
$filters = Services::filters();
$filters->setResponse($this->response);

// After filter debug toolbar requires 'total_execution'.
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');

// Run "after" filters
$this->benchmark->start('after_filters');
$response = $filters->run($uri, 'after');
Expand All @@ -496,21 +536,13 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
! $this->response instanceof DownloadResponse
&& ! $this->response instanceof RedirectResponse
) {
// Cache it without the performance metrics replaced
// so that we can have live speed updates along the way.
// Must be run after filters to preserve the Response headers.
$this->pageCache->make($this->request, $this->response);

// Save our current URI as the previous URI in the session
// for safer, more accurate use with `previous_url()` helper function.
$this->storePreviousURL(current_url(true));
}

unset($uri);

// Is there a post-system event?
Events::trigger('post_system');

return $this->response;
}

Expand Down Expand Up @@ -651,6 +683,8 @@ protected function getResponseObject()
* should be enforced for this URL.
*
* @return void
*
* @deprecated 4.5.0 No longer used. Moved to ForceHTTPS filter.
*/
protected function forceSecureAccess($duration = 31_536_000)
{
Expand All @@ -668,6 +702,7 @@ protected function forceSecureAccess($duration = 31_536_000)
*
* @throws Exception
*
* @deprecated 4.5.0 PageCache required filter is used. No longer used.
* @deprecated 4.4.2 The parameter $config is deprecated. No longer used.
*/
public function displayCache(Cache $config)
Expand Down Expand Up @@ -722,6 +757,9 @@ public function cachePage(Cache $config)
*/
public function getPerformanceStats(): array
{
// After filter debug toolbar requires 'total_execution'.
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');

return [
'startTime' => $this->startTime,
'totalTime' => $this->totalTime,
Expand Down Expand Up @@ -750,6 +788,8 @@ protected function generateCacheName(Cache $config): string

/**
* Replaces the elapsed_time and memory_usage tag.
*
* @deprecated 4.5.0 PerformanceMetrics required filter is used. No longer used.
*/
public function displayPerformanceMetrics(string $output): string
{
Expand All @@ -774,6 +814,8 @@ public function displayPerformanceMetrics(string $output): string
*/
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
{
$this->benchmark->start('routing');

if ($routes === null) {
$routes = Services::routes()->loadRoutes();
}
Expand All @@ -783,9 +825,6 @@ protected function tryToRouteIt(?RouteCollectionInterface $routes = null)

$uri = $this->request->getPath();

$this->benchmark->stop('bootstrap');
$this->benchmark->start('routing');

$this->outputBufferingStart();

$this->controller = $this->router->handle($uri);
Expand Down Expand Up @@ -1052,13 +1091,6 @@ public function spoofRequestMethod()
*/
protected function sendResponse()
{
// Update the performance metrics
$body = $this->response->getBody();
if ($body !== null) {
$output = $this->displayPerformanceMetrics($body);
$this->response->setBody($output);
}

$this->response->send();
}

Expand Down
Loading

0 comments on commit b5451cb

Please sign in to comment.