Optimisations

- Fixed issue with `InputFile` not setting file-name properly.
- Fixed issue with `InputFile` not setting the correct index when
posting certain arrays.
- Made Csrf-token cookie provider more versitile by creating new
`CookieTokenProvider` and `ITokenProvider` classes.
- Strict-checks optimisations.
- Updated documentation to reflect new changes.
This commit is contained in:
Simon Sessingø
2017-11-26 17:32:33 +01:00
parent b9aa348b38
commit 35dc26d741
13 changed files with 211 additions and 88 deletions
+92 -12
View File
@@ -7,9 +7,31 @@ Simple, fast and yet powerful PHP router that is easy to get integrated and in a
The goal of this project is to create a router that is more or less 100% compatible with the Laravel documentation, while remaining as simple as possible, and as easy to integrate and change without compromising either speed or complexity. Being lightweight is the #1 priority.
### Ideas and issues
### Feedback and development
If you want a great new feature or experience any issues what-so-ever, please feel free to leave an issue and i'll look into it whenever possible.
If you are missing a feature, experience problems or have ideas or feedback that you want us to hear, please feel free to create an issue.
###### Issues guidelines
- Please be as detailed as possible in the description when creating a new issue. This will help others to more easily understand- and solve your issue.
For example: if you are experiencing issues, you should provide the necessary steps to reproduce the error within your description.
- We love to hear out any ideas or feedback to the library.
[Create a new issue here](https://github.com/skipperbent/simple-php-router/issues/new)
###### Contribution development guidelines
- Please try to follow the PSR-2 codestyle guidelines.
- Please create your pull requests to the development base that matches the version number you want to change.
For example when pushing changes to version 3, the pull request should use the `v3-development` base/branch.
- Create detailed descriptions for your commits, as these will be used in the changelog for new releases.
- When changing existing functionality, please ensure that the unit-tests working.
- When adding new stuff, please remember to add new unit-tests for the functionality.
---
@@ -51,6 +73,8 @@ If you want a great new feature or experience any issues what-so-ever, please fe
- [CSRF-protection](#csrf-protection)
- [Adding CSRF-verifier](#adding-csrf-verifier)
- [Getting CSRF-token](#getting-csrf-token)
- [Custom CSRF-verifier](#custom-csrf-verifier)
- [Custom Token-provider](#custom-token-provider)
- [Middlewares](#middlewares)
- [Example](#example)
@@ -215,13 +239,13 @@ Simply create a new `web.config` file in your projects `public` directory and pa
#### Troubleshoting
If you do not have a favicon.ico file in your project, you can get `404 Router::notFoundException()` constantly.
To add `favicon.ico` as exception, you can add this line to the `<conditions>` group:
To add `favicon.ico` as exception, you can add this line to the `<conditions>` group:
```<add input="{REQUEST_FILENAME}" negate="true" pattern="favicon.ico" ignoreCase="true" />```
You can also make one exception for files with some extensions:
You can also make one exception for files with some extensions:
```<add input="{REQUEST_FILENAME}" pattern="\.ico|\.png|\.css|\.jpg" negate="true" ignoreCase="true" />```
If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value. By sample:
If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value. By sample:
```/index.php/test/mypage.php```
### Configuration
@@ -681,11 +705,27 @@ SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfoun
# CSRF Protection
Any forms posting to `POST`, `PUT` or `DELETE` routes should include the CSRF-token. We strongly recommend that you create your enable CSRF-verification on your site.
Any forms posting to `POST`, `PUT` or `DELETE` routes should include the CSRF-token. We strongly recommend that you enable CSRF-verification on your site to maximize security.
Create a new class and extend the ```BaseCsrfVerifier``` middleware class provided with simple-php-router.
You can use the `BaseCsrfVerifier` to enable CSRF-validation on all request. If you need to disable verification for specific urls, please refer to the "Custom CSRF-verifier" section below.
Add the property ```except``` with an array of the urls to the routes you would like to exclude/whitelist from the CSRF validation. Using ```*``` at the end for the url will match the entire url.
By default simple-php-router will use the `CookieTokenProvider` class. This provider will store the security-token in a cookie on the clients machine.
If you want to store the token elsewhere, please refer to the "Creating custom Token Provider" section below.
## Adding CSRF-verifier
When you've created your CSRF-verifier you need to tell simple-php-router that it should use it. You can do this by adding the following line in your `routes.php` file:
```php
Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
```
## Custom CSRF-verifier
Create a new class and extend the `BaseCsrfVerifier` middleware class provided by default with the simple-php-router library.
Add the property `except` with an array of the urls to the routes you want to exclude/whitelist from the CSRF validation.
Using ```*``` at the end for the url will match the entire url.
**Here's a basic example on a CSRF-verifier class:**
@@ -703,12 +743,45 @@ class CsrfVerifier extends BaseCsrfVerifier
}
```
## Adding CSRF-verifier
## Custom Token Provider
When you've created your CSRF verifier - you need to tell simple-php-router that it should use it. You can do this by adding the following line in your `routes.php` file:
By default the `BaseCsrfVerifier` will use the `CookieTokenProvider` to store the token in a cookie on the clients machine.
If you need to store the token elsewhere, you can do that by creating your own class and implementing the `ITokenProvider` class.
```php
Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
class SessionTokenProvider implements ITokenProvider
{
/**
* Refresh existing token
*/
public function refresh()
{
// Implement your own functionality here...
}
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate($token)
{
// Implement your own functionality here...
}
}
```
Next you need to set your custom `ITokenProvider` implementation on your `BaseCsrfVerifier` class in your routes file:
```php
$verifier = new \dscuz\Middleware\CsrfVerifier();
$verifier->setTokenProvider(new SessionTokenProvider());
Router::csrfVerifier($verifier);
```
## Getting CSRF-token
@@ -721,6 +794,12 @@ You can get the CSRF-token by calling the helper method:
csrf_token();
```
You can also get the token directly:
```php
return Router::router()->getCsrfVerifier()->getTokenProvider()->getToken();
```
The default name/key for the input-field is `csrf_token` and is defined in the `POST_KEY` constant in the `BaseCsrfVerifier` class.
You can change the key by overwriting the constant in your own CSRF-verifier class.
@@ -1028,6 +1107,7 @@ All object implements the `IInputItem` interface and will always contain these m
- `getValue()` - returns the value of the input.
`InputFile` has the same methods as above along with some other file-specific methods like:
- `getFilename` - get the filename.
- `getTmpName()` - get file temporary name.
- `getSize()` - get file size.
- `move($destination)` - move file to destination.
@@ -1051,7 +1131,7 @@ $siteId = input('site_id', 2, ['post', 'get']);
## Url rewriting
Sometimes it can be useful to manipulate the route about to be loaded.
simple-php-router allows you to easily change the route about to be executed.
All information about the current route is stored in the ```\Pecee\SimpleRouter\Router``` instance's `loadedRoute` property.
All information about the current route is stored in the `\Pecee\SimpleRouter\Router` instance's `loadedRoute` property.
For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`.
+1 -1
View File
@@ -73,7 +73,7 @@ function csrf_token()
{
$baseVerifier = Router::router()->getCsrfVerifier();
if ($baseVerifier !== null) {
return $baseVerifier->getToken();
return $baseVerifier->getTokenProvider()->getToken();
}
return null;
+12 -9
View File
@@ -35,7 +35,7 @@ class Input
public function parseInputs()
{
/* Parse get requests */
if (count($_GET) > 0) {
if (count($_GET) !== 0) {
$this->get = $this->handleGetPost($_GET);
}
@@ -46,12 +46,12 @@ class Input
parse_str(file_get_contents('php://input'), $postVars);
}
if (count($postVars) > 0) {
if (count($postVars) !== 0) {
$this->post = $this->handleGetPost($postVars);
}
/* Parse get requests */
if (count($_FILES) > 0) {
if (count($_FILES) !== 0) {
$this->file = $this->parseFiles();
}
}
@@ -69,7 +69,7 @@ class Input
continue;
}
$keys = [];
$keys = [$key];
$files = $this->rearrangeFiles($value['name'], $keys, $value);
@@ -87,6 +87,9 @@ class Input
protected function rearrangeFiles(array $values, &$index, $original)
{
$originalIndex = $index[0];
array_shift($index);
$output = [];
$getItem = function ($key, $property = 'name') use ($original, $index) {
@@ -107,7 +110,7 @@ class Input
if (is_array($getItem($key)) === false) {
$file = InputFile::createFromArray([
'index' => $key,
'index' => (empty($key) === true && empty($originalIndex) === false) ? $originalIndex : $key,
'filename' => $getItem($key),
'error' => $getItem($key, 'error'),
'tmp_name' => $getItem($key, 'tmp_name'),
@@ -128,7 +131,7 @@ class Input
$files = $this->rearrangeFiles($value, $index, $original);
if (isset($output[$key])) {
if (isset($output[$key]) === true) {
$output[$key][] = $files;
} else {
$output[$key] = $files;
@@ -217,15 +220,15 @@ class Input
$element = null;
if ($methods === null || in_array('get', $methods)) {
if ($methods === null || in_array('get', $methods, false) === true) {
$element = $this->findGet($index);
}
if (($element === null && $methods === null) || ($methods !== null && in_array('post', $methods))) {
if (($element === null && $methods === null) || ($methods !== null && in_array('post', $methods, false) === true)) {
$element = $this->findPost($index);
}
if (($element === null && $methods === null) || ($methods !== null && in_array('file', $methods))) {
if (($element === null && $methods === null) || ($methods !== null && in_array('file', $methods, false) === true)) {
$element = $this->findFile($index);
}
+6 -4
View File
@@ -16,7 +16,7 @@ class InputFile implements IInputItem
$this->index = $index;
// Make the name human friendly, by replace _ with space
$this->name = ucfirst(str_replace('_', ' ', $this->index));
$this->name = ucfirst(str_replace('_', ' ', strtolower($this->index)));
}
/**
@@ -28,7 +28,7 @@ class InputFile implements IInputItem
*/
public static function createFromArray(array $values)
{
if (!isset($values['index'])) {
if (array_key_exists('index', $values) === false) {
throw new \InvalidArgumentException('Index key is required');
}
@@ -39,6 +39,7 @@ class InputFile implements IInputItem
'type' => null,
'size' => null,
'name' => null,
'filename' => null,
'error' => null,
], $values);
@@ -47,7 +48,7 @@ class InputFile implements IInputItem
->setError($values['error'])
->setType($values['type'])
->setTmpName($values['tmp_name'])
->setFilename($values['name']);
->setFilename($values['filename']);
}
@@ -267,8 +268,9 @@ class InputFile implements IInputItem
'tmp_name' => $this->tmpName,
'type' => $this->type,
'size' => $this->size,
'name' => $this->filename,
'name' => $this->name,
'error' => $this->error,
'filename' => $this->filename,
];
}
+1 -1
View File
@@ -13,7 +13,7 @@ class InputItem implements IInputItem
$this->value = $value;
// Make the name human friendly, by replace _ with space
$this->name = ucfirst(str_replace('_', ' ', $this->index));
$this->name = ucfirst(str_replace('_', ' ', strtolower($this->index)));
}
/**
+16 -27
View File
@@ -2,9 +2,10 @@
namespace Pecee\Http\Middleware;
use Pecee\CsrfToken;
use Pecee\Http\Middleware\Exceptions\TokenMismatchException;
use Pecee\Http\Request;
use Pecee\Http\Security\CookieTokenProvider;
use Pecee\Http\Security\ITokenProvider;
class BaseCsrfVerifier implements IMiddleware
{
@@ -12,15 +13,11 @@ class BaseCsrfVerifier implements IMiddleware
const HEADER_KEY = 'X-CSRF-TOKEN';
protected $except;
protected $csrfToken;
protected $token;
protected $tokenProvider;
public function __construct()
{
$this->csrfToken = new CsrfToken();
// Generate or get the CSRF-Token from Cookie.
$this->token = $this->csrfToken->getToken($this->generateToken());
$this->tokenProvider = new CookieTokenProvider();
}
/**
@@ -30,7 +27,7 @@ class BaseCsrfVerifier implements IMiddleware
*/
protected function skip(Request $request)
{
if ($this->except === null || is_array($this->except) === false) {
if ($this->except === null || count($this->except) === 0) {
return false;
}
@@ -67,37 +64,29 @@ class BaseCsrfVerifier implements IMiddleware
$token = $request->getHeader(static::HEADER_KEY);
}
if ($this->csrfToken->validate($token) === false) {
throw new TokenMismatchException('Invalid csrf-token.');
if ($this->tokenProvider->validate($token) === false) {
throw new TokenMismatchException('Invalid CSRF-token.');
}
}
// Refresh existing token
$this->csrfToken->refresh();
$this->tokenProvider->refresh();
}
public function generateToken()
public function getTokenProvider()
{
$token = CsrfToken::generateToken();
$this->csrfToken->setToken($token);
return $token;
return $this->tokenProvider;
}
public function hasToken()
/**
* Set token provider
* @param ITokenProvider $provider
*/
public function setTokenProvider(ITokenProvider $provider)
{
if ($this->token !== null) {
return true;
}
return $this->csrfToken->hasToken();
}
public function getToken()
{
return $this->token;
$this->tokenProvider = $provider;
}
}
@@ -1,12 +1,22 @@
<?php
namespace Pecee;
namespace Pecee\Http\Security;
class CsrfToken
class CookieTokenProvider implements ITokenProvider
{
const CSRF_KEY = 'CSRF-TOKEN';
protected $token;
protected $cookieTimeoutMinutes = 120;
public function __construct()
{
$this->token = $this->getToken();
if ($this->token === null) {
$this->token = $this->generateToken();
}
}
/**
* Generate random identifier for CSRF token
@@ -14,7 +24,7 @@ class CsrfToken
* @throws \RuntimeException
* @return string
*/
public static function generateToken()
public function generateToken()
{
if (function_exists('random_bytes') === true) {
return bin2hex(random_bytes(32));
@@ -54,7 +64,7 @@ class CsrfToken
public function setToken($token)
{
$this->token = $token;
setcookie(static::CSRF_KEY, $token, time() + 60 * 120, '/');
setcookie(static::CSRF_KEY, $token, time() + 60 * $this->cookieTimeoutMinutes, '/');
}
/**
@@ -88,4 +98,22 @@ class CsrfToken
return isset($_COOKIE[static::CSRF_KEY]);
}
/**
* Get timeout for cookie in minutes
* @return int
*/
public function getCookieTimeoutMinutes()
{
return $this->cookieTimeoutMinutes;
}
/**
* Set cookie timeout in minutes
* @param $minutes
*/
public function setCookieTimeoutMinutes($minutes)
{
$this->cookieTimeoutMinutes = $minutes;
}
}
@@ -0,0 +1,21 @@
<?php
namespace Pecee\Http\Security;
interface ITokenProvider
{
/**
* Refresh existing token
*/
public function refresh();
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate($token);
}
@@ -30,7 +30,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
{
$max = count($this->getMiddlewares());
if ($max > 0) {
if ($max !== 0) {
for ($i = 0; $i < $max; $i++) {
@@ -57,7 +57,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
return null;
}
return (preg_match($this->regex, $request->getHost() . $url) > 0);
return (preg_match($this->regex, $request->getHost() . $url) !== 0);
}
/**
@@ -74,7 +74,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
if (preg_match_all('/' . $regex . '/u', $this->url, $matches)) {
if (preg_match_all('/' . $regex . '/u', $this->url, $matches) === 1) {
$this->parameters = array_fill_keys($matches[1], null);
}
}
@@ -102,7 +102,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) > 0) {
if ($group !== null && count($group->getDomains()) !== 0) {
$url = '//' . $group->getDomains()[0] . $url;
}
+13 -13
View File
@@ -117,7 +117,7 @@ abstract class Route implements IRoute
// Ensures that hostnames/domains will work with parameters
$url = '/' . ltrim($url, '/');
if (preg_match_all('/' . $regex . '/u', $route, $parameters)) {
if (preg_match_all('/' . $regex . '/u', $route, $parameters) !== 0) {
$urlParts = preg_split('/((\-?\/?)\{[^}]+\})/', rtrim($route, '/'));
@@ -155,7 +155,7 @@ abstract class Route implements IRoute
$urlRegex = preg_quote($route, '/');
}
if (preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) > 0) {
if (preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === 1) {
$values = [];
@@ -361,15 +361,15 @@ abstract class Route implements IRoute
$values['namespace'] = $this->namespace;
}
if (count($this->requestMethods) > 0) {
if (count($this->requestMethods) !== 0) {
$values['method'] = $this->requestMethods;
}
if (count($this->where) > 0) {
if (count($this->where) !== 0) {
$values['where'] = $this->where;
}
if (count($this->middlewares) > 0) {
if (count($this->middlewares) !== 0) {
$values['middleware'] = $this->middlewares;
}
@@ -389,28 +389,28 @@ abstract class Route implements IRoute
*/
public function setSettings(array $values, $merge = false)
{
if ($this->namespace === null && isset($values['namespace'])) {
if ($this->namespace === null && isset($values['namespace']) === true) {
$this->setNamespace($values['namespace']);
}
if (isset($values['method'])) {
if (isset($values['method']) === true) {
$this->setRequestMethods(array_merge($this->requestMethods, (array)$values['method']));
}
if (isset($values['where'])) {
if (isset($values['where']) === true) {
$this->setWhere(array_merge($this->where, (array)$values['where']));
}
if (isset($values['parameters'])) {
if (isset($values['parameters']) === true) {
$this->setParameters(array_merge($this->parameters, (array)$values['parameters']));
}
// Push middleware if multiple
if (isset($values['middleware'])) {
if (isset($values['middleware']) === true) {
$this->setMiddlewares(array_merge((array)$values['middleware'], $this->middlewares));
}
if (isset($values['defaultParameterRegex'])) {
if (isset($values['defaultParameterRegex']) === true) {
$this->setDefaultParameterRegex($values['defaultParameterRegex']);
}
@@ -463,7 +463,7 @@ abstract class Route implements IRoute
/* Sort the parameters after the user-defined param order, if any */
$parameters = [];
if (count($this->originalParameters) > 0) {
if (count($this->originalParameters) !== 0) {
$parameters = $this->originalParameters;
}
@@ -482,7 +482,7 @@ abstract class Route implements IRoute
* If this is the first time setting parameters we store them so we
* later can organize the array, in case somebody tried to sort the array.
*/
if (count($parameters) > 0 && count($this->originalParameters) === 0) {
if (count($parameters) !== 0 && count($this->originalParameters) === 0) {
$this->originalParameters = $parameters;
}
@@ -76,7 +76,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) > 0) {
if ($group !== null && count($group->getDomains()) !== 0) {
$url .= '//' . $group->getDomains()[0];
}
@@ -97,7 +97,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$path = explode('/', $strippedUrl);
if (count($path) > 0) {
if (count($path) !== 0) {
$method = (isset($path[0]) === false || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $request->getMethod() . ucfirst($method);
+6 -6
View File
@@ -28,7 +28,7 @@ class RouteGroup extends Route implements IGroupRoute
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if ($parameters !== null && count($parameters) > 0) {
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
@@ -146,19 +146,19 @@ class RouteGroup extends Route implements IGroupRoute
public function setSettings(array $values, $merge = false)
{
if (isset($values['prefix'])) {
if (isset($values['prefix']) === true) {
$this->setPrefix($values['prefix'] . $this->prefix);
}
if ($merge === false && isset($values['exceptionHandler'])) {
if ($merge === false && isset($values['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
}
if ($merge === false && isset($values['domain'])) {
if ($merge === false && isset($values['domain']) === true) {
$this->setDomains((array)$values['domain']);
}
if (isset($values['as'])) {
if (isset($values['as']) === true) {
if ($this->name !== null && $merge !== false) {
$this->name = $values['as'] . '.' . $this->name;
} else {
@@ -188,7 +188,7 @@ class RouteGroup extends Route implements IGroupRoute
$values['as'] = $this->name;
}
if (count($this->parameters) > 0) {
if (count($this->parameters) !== 0) {
$values['parameters'] = $this->parameters;
}
+5 -5
View File
@@ -154,7 +154,7 @@ class Router
if ($route->matchRoute($url, $this->request) === true) {
/* Add exception handlers */
if (count($route->getExceptionHandlers()) > 0) {
if (count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
}
@@ -181,7 +181,7 @@ class Router
$this->processedRoutes[] = $route;
}
if (count($this->routeStack) > 0) {
if (count($this->routeStack) !== 0) {
/* Pop and grab the routes added when executing group callback earlier */
$stack = $this->routeStack;
@@ -203,7 +203,7 @@ class Router
public function loadRoutes()
{
/* Initialize boot-managers */
if (count($this->bootManagers) > 0) {
if (count($this->bootManagers) !== 0) {
$max = count($this->bootManagers) - 1;
@@ -247,7 +247,7 @@ class Router
if ($route->matchRoute($url, $this->request) === true) {
/* Check if request method matches */
if (count($route->getRequestMethods()) > 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), false) === false) {
if (count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), false) === false) {
$routeNotAllowed = true;
continue;
}
@@ -363,7 +363,7 @@ class Router
public function arrayToParams(array $getParams = [], $includeEmpty = true)
{
if (count($getParams) > 0) {
if (count($getParams) !== 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, function ($item) {