Compare commits

...

45 Commits

Author SHA1 Message Date
Simon Sessingø 3ba9dd01f0 Merge pull request #360 from skipperbent/v3-development
Version 3.4.11.0
2018-02-14 12:39:19 +01:00
Simon Sessingø 932dfbf2b7 Merge pull request #357 from skipperbent/v3-optimisations
Optimisations
2018-02-14 12:38:07 +01:00
Simon Sessingo 8d87aab35b Optimisations 2018-02-05 05:27:47 +01:00
Simon Sessingø bbb81333b4 Merge pull request #352 from skipperbent/v3-development
Fixed Input::all when php://input is not available
2018-01-14 15:54:37 +01:00
Simon Sessingo fc2e2e1e82 Fixed Input::all when php://input is not availible 2018-01-14 15:53:44 +01:00
Simon Sessingø 9a7b598880 Merge pull request #350 from skipperbent/v3-development
Version 3.4.10.0
2018-01-06 03:33:13 +01:00
Simon Sessingø 71518431a9 Merge pull request #348 from skipperbent/v3-fix-default-value
Fixed parameter default-value being overwritten (issue: #347).
2018-01-06 03:31:03 +01:00
Simon Sessingø cec240ab0c Merge pull request #349 from skipperbent/feature-get-processed-routes
Added getProcessedRoutes method to Router class.
2018-01-06 03:30:55 +01:00
Simon Sessingo 4d2b584936 Fixed parameter default-value being overwritten (issue: #347). 2018-01-06 03:27:27 +01:00
Simon Sessingo a102c70700 Added getProcessedRoutes method to Router class. 2018-01-06 01:23:00 +01:00
Simon Sessingø 2db20dce6b Merge pull request #344 from skipperbent/v3-development
Version 3.4.9.3
2017-12-17 10:41:22 +01:00
Simon Sessingo f2d106c649 _method is not supported as fallback for every request-type. 2017-12-17 10:37:33 +01:00
Simon Sessingø c6341960d7 Merge pull request #341 from skipperbent/v3-development
Version 3.4.9.2
2017-12-17 09:53:39 +01:00
Simon Sessingo 72d33dd497 Compatibility: setUri can be both string and Uri object. 2017-12-17 09:51:56 +01:00
Simon Sessingo e23dd37435 Reverted changes 2017-12-16 23:29:00 +01:00
Simon Sessingo aa5ec47051 Removed key check in __get as it's already performed in the __isset method. 2017-12-16 23:24:31 +01:00
Simon Sessingø 85cf925793 Merge pull request #339 from skipperbent/v3-development
Version 3.4.9.1
2017-12-16 12:53:56 +01:00
Simon Sessingo 155729074b Fixed filtering on get-params in all method. 2017-12-16 12:48:10 +01:00
Simon Sessingø 7c0a20115e Merge pull request #336 from skipperbent/v3-development
Version 3.4.9.0
2017-12-09 23:58:33 +01:00
Simon Sessingø 0be7bfcfd9 Fixed utf-8 header issue (issue: #333) 2017-12-09 23:56:37 +01:00
Simon Sessingø 877e0aa937 Merge pull request #334 from skipperbent/v3-development
Version 3.4.8.0
2017-12-06 19:06:09 +01:00
Simon Sessingø 7f8d90eef8 Added $options and $debt param to json method in Response class. 2017-12-06 19:04:48 +01:00
Simon Sessingø 3e7767d978 Merge pull request #330 from skipperbent/v3-development
V3 development
2017-12-02 20:30:27 +01:00
Simon Sessingø 4bb784bcec Merge pull request #329 from skipperbent/v3-optimisations
Optimisations
2017-12-02 20:30:02 +01:00
Simon Sessingo c4ee1b9186 Optimisations 2017-12-02 19:30:30 +01:00
Simon Sessingø 5ef9349b18 Merge pull request #327 from skipperbent/v3-development
Version 3.4.6.4
2017-11-27 02:05:17 +01:00
Simon Sessingø efd5159604 Bugfixes
- Fixed issue with parsing parameters on some occasion.
- Optimisations.
2017-11-27 02:03:12 +01:00
Simon Sessingø 264f0a7b0f Merge pull request #325 from skipperbent/v3-development
Version 3.4.6.3
2017-11-27 00:01:30 +01:00
Simon Sessingø bdfc36ed5c More Input-related optimisations. 2017-11-27 00:00:19 +01:00
Simon Sessingø 5197ae2990 Merge pull request #323 from skipperbent/v3-development
Version 3.4.6.2
2017-11-26 23:44:06 +01:00
Simon Sessingø d921ae8105 Bugfixes
- Fixed minor issues with File Input-parsing.
- Optimized Input class.
2017-11-26 23:42:28 +01:00
Simon Sessingø 9c701aabee Merge pull request #321 from skipperbent/v3-development
Fixed url parsing issues
2017-11-26 18:45:54 +01:00
Simon Sessingø af2be14ccb Fixed url parsing issues 2017-11-26 18:45:06 +01:00
Simon Sessingø 2b0821a557 Merge pull request #320 from skipperbent/v3-development
Fixed typo
2017-11-26 18:29:36 +01:00
Simon Sessingø fae2e84c98 Fixed typo 2017-11-26 18:29:00 +01:00
Simon Sessingø afc81d7005 Merge pull request #318 from skipperbent/v3-development
Version 3.4.6.0
2017-11-26 18:08:24 +01:00
Simon Sessingø c90c74b88f Merge pull request #317 from skipperbent/v3-optimisations
V3 optimisations
2017-11-26 18:06:37 +01:00
Simon Sessingø 05f2493304 Development
- Optimized Uri class.
- Request class now uses SimpleRouter to get default-namespace as was
originally the intended behavior.
- Documentation changes.
2017-11-26 18:03:14 +01:00
Simon Sessingø 0856caa9de Merge branch 'v3-development' of github.com:skipperbent/simple-php-router into v3-optimisations 2017-11-26 17:38:49 +01:00
Simon Sessingø 35dc26d741 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.
2017-11-26 17:32:33 +01:00
Simon Sessingø 496d3e7182 Merge pull request #315 from skipperbent/v3-development
Version 3.4.5.5
2017-11-25 01:34:53 +01:00
Simon Sessingø f2819f866e Merge pull request #314 from skipperbent/v3-documentation-spelling
Fixed spelling in documentation
2017-11-25 01:34:01 +01:00
Simon Sessingø b9aa348b38 Merge branch 'v3-development' of github.com:skipperbent/simple-php-router into v3-development 2017-11-25 01:32:24 +01:00
Simon Sessingø c29c52ae16 Fixed default-namespace causing router to break on closures. 2017-11-25 01:31:43 +01:00
Simon Sessingo 6547c07113 Fixed spelling in documentation 2017-11-20 21:53:41 +01:00
20 changed files with 402 additions and 285 deletions
+122 -36
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)
@@ -212,17 +236,23 @@ Simply create a new `web.config` file in your projects `public` directory and pa
</configuration>
```
#### Troubleshoting
#### Troubleshooting
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:
```<add input="{REQUEST_FILENAME}" negate="true" pattern="favicon.ico" ignoreCase="true" />```
If you do not have a `favicon.ico` file in your project, you can get a `NotFoundHttpException` (404 - not found).
To add `favicon.ico` to the IIS ignore-list, add the following 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:
```<add input="{REQUEST_FILENAME}" pattern="\.ico|\.png|\.css|\.jpg" negate="true" ignoreCase="true" />```
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:
```/index.php/test/mypage.php```
If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value. For example:
```
/index.php/test/mypage.php
```
### Configuration
@@ -681,11 +711,57 @@ 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());
```
## Getting CSRF-token
When posting to any of the urls that has CSRF-verification enabled, you need post your CSRF-token or else the request will get rejected.
You can get the CSRF-token by calling the helper method:
```php
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.
**Example:**
The example below will post to the current url with a hidden field "`csrf_token`".
```html
<form method="post" action="<?= url(); ?>">
<input type="hidden" name="csrf_token" value="<?= csrf_token(); ?>">
<!-- other input elements here -->
</form>
```
## 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,36 +779,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...
}
}
```
## Getting CSRF-token
When posting to any of the urls that has CSRF-verification enabled, you need post your CSRF-token or else the request will get rejected.
You can get the CSRF-token by calling the helper method:
Next you need to set your custom `ITokenProvider` implementation on your `BaseCsrfVerifier` class in your routes file:
```php
csrf_token();
```
$verifier = new \dscuz\Middleware\CsrfVerifier();
$verifier->setTokenProvider(new SessionTokenProvider());
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.
**Example:**
The example below will post to the current url with a hidden field "`csrf_token`".
```html
<form method="post" action="<?= url(); ?>">
<input type="hidden" name="csrf_token" value="<?= csrf_token(); ?>">
<!-- other input elements here -->
</form>
Router::csrfVerifier($verifier);
```
---
@@ -1028,6 +1113,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 +1137,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()`.
+6 -1
View File
@@ -9,7 +9,12 @@
"simple-php-router",
"laravel",
"pecee",
"php"
"php",
"framework",
"url-handling",
"input-handler",
"routing-engine",
"request-handler"
],
"license": "MIT",
"support": {
+2 -1
View File
@@ -18,6 +18,7 @@ use Pecee\SimpleRouter\SimpleRouter as Router;
* @param string|array|null $parameters
* @param array|null $getParams
* @return string
* @throws \InvalidArgumentException
*/
function url($name = null, $parameters = null, $getParams = null)
{
@@ -73,7 +74,7 @@ function csrf_token()
{
$baseVerifier = Router::router()->getCsrfVerifier();
if ($baseVerifier !== null) {
return $baseVerifier->getToken();
return $baseVerifier->getTokenProvider()->getToken();
}
return null;
+30 -45
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\Http\Input;
use Pecee\Http\Request;
@@ -35,7 +36,7 @@ class Input
public function parseInputs()
{
/* Parse get requests */
if (count($_GET) > 0) {
if (count($_GET) !== 0) {
$this->get = $this->handleGetPost($_GET);
}
@@ -46,12 +47,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();
}
}
@@ -65,15 +66,15 @@ class Input
// Handle array input
if (is_array($value['name']) === false) {
$values['index'] = $key;
$list[$key] = InputFile::createFromArray(array_merge($value, $values));
$list[$key] = InputFile::createFromArray($values + $value);
continue;
}
$keys = [];
$keys = [$key];
$files = $this->rearrangeFiles($value['name'], $keys, $value);
if (isset($list[$key])) {
if (isset($list[$key]) === true) {
$list[$key][] = $files;
} else {
$list[$key] = $files;
@@ -87,40 +88,30 @@ class Input
protected function rearrangeFiles(array $values, &$index, $original)
{
$originalIndex = $index[0];
array_shift($index);
$output = [];
$getItem = function ($key, $property = 'name') use ($original, $index) {
$path = $original[$property];
$fileValues = array_values($index);
foreach ($fileValues as $i) {
$path = $path[$i];
}
return $path[$key];
};
foreach ($values as $key => $value) {
if (is_array($getItem($key)) === false) {
if (is_array($original['name'][$key]) === false) {
$file = InputFile::createFromArray([
'index' => $key,
'filename' => $getItem($key),
'error' => $getItem($key, 'error'),
'tmp_name' => $getItem($key, 'tmp_name'),
'type' => $getItem($key, 'type'),
'size' => $getItem($key, 'size'),
'index' => (empty($key) === true && empty($originalIndex) === false) ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$key],
'tmp_name' => $original['tmp_name'][$key],
'type' => $original['type'][$key],
'size' => $original['size'][$key],
]);
if (isset($output[$key])) {
if (isset($output[$key]) === true) {
$output[$key][] = $file;
} else {
$output[$key] = $file;
continue;
}
$output[$key] = $file;
continue;
}
@@ -128,7 +119,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;
@@ -143,13 +134,7 @@ class Input
{
$list = [];
$max = count($array) - 1;
$keys = array_keys($array);
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$value = $array[$key];
foreach ($array as $key => $value) {
// Handle array input
if (is_array($value) === false) {
@@ -217,15 +202,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);
}
@@ -269,21 +254,21 @@ class Input
*/
public function all(array $filter = null)
{
$output = $_POST;
$output = $_GET + $_POST;
if ($this->request->getMethod() === 'post') {
$contents = file_get_contents('php://input');
if (strpos(trim($contents), '{') === 0) {
$output = json_decode($contents, true);
if ($output === false) {
$output = [];
$post = json_decode($contents, true);
if ($post !== false) {
$output += $post;
}
}
}
return ($filter !== null) ? array_intersect_key($output, array_flip($filter)) : array_merge($_GET, $output);
return ($filter !== null) ? array_intersect_key($output, array_flip($filter)) : $output;
}
}
+7 -5
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\Http\Input;
class InputFile implements IInputItem
@@ -16,7 +17,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,19 +29,19 @@ class InputFile implements IInputItem
*/
public static function createFromArray(array $values)
{
if (!isset($values['index'])) {
if (isset($values['index']) === false) {
throw new \InvalidArgumentException('Index key is required');
}
/* Easy way of ensuring that all indexes-are set and not filling the screen with isset() */
$values = array_merge([
$values += [
'tmp_name' => null,
'type' => null,
'size' => null,
'name' => null,
'error' => null,
], $values);
];
return (new static($values['index']))
->setSize($values['size'])
@@ -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)));
}
/**
+22 -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;
}
@@ -55,6 +52,12 @@ class BaseCsrfVerifier implements IMiddleware
return false;
}
/**
* Handle request
*
* @param Request $request
* @throws TokenMismatchException
*/
public function handle(Request $request)
{
@@ -67,37 +70,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;
}
}
+8 -23
View File
@@ -36,7 +36,7 @@ class Request
$this->setUri(new Uri($this->getHeader('unencoded-url', $this->getHeader('request-uri'))));
$this->input = new Input($this);
$this->method = strtolower($this->input->get('_method', $this->getHeader('request-method'), 'post'));
$this->method = strtolower($this->input->get('_method', $this->getHeader('request-method')));
}
protected function parseHeaders()
@@ -196,10 +196,14 @@ class Request
}
/**
* @param Uri $uri
* @param Uri|string $uri
*/
public function setUri(Uri $uri)
public function setUri($uri)
{
if (is_string($uri) === true) {
$uri = new Uri($uri);
}
$this->uri = $uri;
}
@@ -227,26 +231,7 @@ class Request
*/
public function setRewriteRoute(ILoadableRoute $route)
{
$this->rewriteRoute = $route;
$callback = $route->getCallback();
/* Only add default namespace on relative callbacks */
if ($callback === null || $callback[0] !== '\\') {
$namespace = SimpleRouter::getDefaultNamespace();
if ($namespace !== null) {
if ($this->rewriteRoute->getNamespace() !== null) {
$namespace .= '\\' . $this->rewriteRoute->getNamespace();
}
$this->rewriteRoute->setDefaultNamespace($namespace);
}
}
$this->rewriteRoute = SimpleRouter::addDefaultNamespace($route);
return $this;
}
+6 -4
View File
@@ -85,16 +85,18 @@ class Response
/**
* Json encode
* @param array|\JsonSerializable $value
* @throws \InvalidArgumentException;
* @param int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $dept JSON debt.
* @throws \InvalidArgumentException
*/
public function json($value)
public function json($value, $options = null, $dept = 512)
{
if (($value instanceof \JsonSerializable) === false && is_array($value) === false) {
throw new \InvalidArgumentException('Invalid type for parameter "value". Must be of type array or object implementing the \JsonSerializable interface.');
}
$this->header('Content-type: application/json');
echo json_encode($value);
$this->header('Content-Type: application/json; charset=utf-8');
echo json_encode($value, $options, $dept);
exit(0);
}
@@ -1,20 +1,30 @@
<?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
*
* @throws \RuntimeException
* @throws \RuntimeException|\Exception
* @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);
}
+11 -15
View File
@@ -6,20 +6,20 @@ class Uri
{
private $originalUrl;
private $data = [
'scheme',
'host',
'port',
'user',
'pass',
'path',
'query',
'fragment',
'scheme' => null,
'host' => null,
'port' => null,
'user' => null,
'pass' => null,
'path' => null,
'query' => null,
'fragment' => null,
];
public function __construct($url)
{
$this->originalUrl = $url;
$this->data = array_merge($this->data, $this->parseUrl(urldecode($url)));
$this->data = $this->parseUrl($url) + $this->data;
if (isset($this->data['path']) === true && $this->data['path'] !== '/') {
$this->data['path'] = rtrim($this->data['path'], '/') . '/';
@@ -135,7 +135,7 @@ class Uri
public function parseUrl($url, $component = -1)
{
$encodedUrl = preg_replace_callback(
'%[^:/@?&=#]+%u',
'/[^:\/@?&=#]+/u',
function ($matches) {
return urlencode($matches[0]);
},
@@ -148,11 +148,7 @@ class Uri
throw new \InvalidArgumentException('Malformed URL: ' . $url);
}
foreach ((array)$parts as $name => $value) {
$parts[$name] = urldecode($value);
}
return $parts;
return array_map('urldecode', $parts);
}
/**
+21 -25
View File
@@ -28,24 +28,17 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/
public function loadMiddleware(Request $request)
{
$max = count($this->getMiddlewares());
foreach ($this->getMiddlewares() as $middleware) {
if ($max > 0) {
for ($i = 0; $i < $max; $i++) {
$middleware = $this->getMiddlewares()[$i];
if (is_object($middleware) === false) {
$middleware = $this->loadClass($middleware);
}
if (($middleware instanceof IMiddleware) === false) {
throw new HttpException($middleware . ' must be inherit the IMiddleware interface');
}
$middleware->handle($request);
if (is_object($middleware) === false) {
$middleware = $this->loadClass($middleware);
}
if (($middleware instanceof IMiddleware) === false) {
throw new HttpException($middleware . ' must be inherit the IMiddleware interface');
}
$middleware->handle($request);
}
}
@@ -74,7 +67,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) > 0) {
$this->parameters = array_fill_keys($matches[1], null);
}
}
@@ -102,7 +95,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;
}
@@ -124,7 +117,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
for ($i = $max; $i >= 0; $i--) {
$param = $keys[$i];
if ($parameters === '' || (is_array($parameters) && count($parameters) === 0)) {
if ($parameters === '' || (is_array($parameters) === true && count($parameters) === 0)) {
$value = '';
} else {
$p = (array)$parameters;
@@ -145,7 +138,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
}
$url = '/' . ltrim($url, '/');
$url .= join('/', $unknownParams);
$url .= implode('/', $unknownParams);
return rtrim($url, '/') . '/';
}
@@ -229,15 +222,18 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['as'])) {
if (isset($values['as']) === true) {
$name = $values['as'];
if ($this->name !== null && $merge !== false) {
$this->setName($values['as'] . '.' . $this->name);
} else {
$this->setName($values['as']);
$name .= '.' . $this->name;
}
$this->setName($name);
}
if (isset($values['prefix'])) {
if (isset($values['prefix']) === true) {
$this->setUrl($values['prefix'] . $this->getUrl());
}
+40 -29
View File
@@ -33,7 +33,7 @@ abstract class Route implements IRoute
*
* @var bool
*/
protected $filterEmptyParams = false;
protected $filterEmptyParams = true;
/**
* Default regular expression used for parsing parameters.
@@ -56,6 +56,12 @@ abstract class Route implements IRoute
protected $originalParameters = [];
protected $middlewares = [];
/**
* Load class by name
* @param string $name
* @return mixed
* @throws NotFoundHttpException
*/
protected function loadClass($name)
{
if (class_exists($name) === false) {
@@ -65,6 +71,13 @@ abstract class Route implements IRoute
return new $name();
}
/**
* Render route
*
* @param Request $request
* @return string|mixed
* @throws NotFoundHttpException
*/
public function renderRoute(Request $request)
{
$callback = $this->getCallback();
@@ -73,12 +86,20 @@ abstract class Route implements IRoute
return null;
}
$parameters = $this->getParameters();
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, function ($var) {
return ($var !== null);
});
}
/* Render callback function */
if (is_callable($callback) === true) {
/* When the callback is a function */
return call_user_func_array($callback, $this->getParameters());
return call_user_func_array($callback, $parameters);
}
/* When the callback is a class + method */
@@ -95,16 +116,6 @@ abstract class Route implements IRoute
throw new NotFoundHttpException(sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404);
}
$parameters = $this->getParameters();
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, function ($var) {
return ($var !== null);
});
}
return call_user_func_array([$class, $method], $parameters);
}
@@ -117,9 +128,9 @@ 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, '/'));
$urlParts = preg_split('/((\-?\/?)\{[^}]+\})/', $route);
foreach ($urlParts as $key => $t) {
@@ -149,7 +160,7 @@ abstract class Route implements IRoute
$urlParts[$key] = preg_quote($t, '/') . $regex;
}
$urlRegex = join('', $urlParts);
$urlRegex = implode('', $urlParts);
} else {
$urlRegex = preg_quote($route, '/');
@@ -361,15 +372,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 +400,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 +474,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 +493,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;
}
@@ -532,7 +543,7 @@ abstract class Route implements IRoute
}
/**
* @return string|array
* @return array
*/
public function getMiddlewares()
{
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
@@ -76,11 +77,11 @@ 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];
}
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . join('/', $parameters);
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/';
}
@@ -97,7 +98,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);
@@ -168,7 +169,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['names'])) {
if (isset($values['names']) === true) {
$this->names = $values['names'];
}
+12 -9
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,24 +146,27 @@ 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) {
$name = $values['as'];
if ($this->name !== null && $merge !== false) {
$this->name = $values['as'] . '.' . $this->name;
} else {
$this->name = $values['as'];
$name .= '.' . $this->name;
}
$this->name = $name;
}
parent::setSettings($values, $merge);
@@ -188,7 +191,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;
}
@@ -206,11 +206,11 @@ class RouteResource extends LoadableRoute implements IControllerRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['names'])) {
if (isset($values['names']) === true) {
$this->names = $values['names'];
}
if (isset($values['methods'])) {
if (isset($values['methods']) === true) {
$this->methodNames = $values['methods'];
}
+35 -48
View File
@@ -16,12 +16,6 @@ use Pecee\SimpleRouter\Route\IRoute;
class Router
{
/**
* The instance of this class
* @var static
*/
protected static $instance;
/**
* Current request
* @var Request
@@ -119,16 +113,12 @@ class Router
protected function processRoutes(array $routes, IGroupRoute $group = null, IRoute $parent = null)
{
// Loop through each route-request
$max = count($routes) - 1;
$exceptionHandlers = [];
$url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath();
/* @var $route IRoute */
for ($i = $max; $i >= 0; $i--) {
$route = $routes[$i];
foreach ($routes as $route) {
if ($parent !== null) {
@@ -154,7 +144,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 +171,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,21 +193,23 @@ class Router
public function loadRoutes()
{
/* Initialize boot-managers */
if (count($this->bootManagers) > 0) {
$max = count($this->bootManagers) - 1;
/* @var $manager IRouterBootManager */
for ($i = $max; $i >= 0; $i--) {
$manager = $this->bootManagers[$i];
$manager->boot($this->request);
}
/* @var $manager IRouterBootManager */
foreach ($this->bootManagers as $manager) {
$manager->boot($this->request);
}
/* Loop through each route-request */
$this->processRoutes($this->routes);
}
/**
* Routes the request
*
* @param bool $rewrite
* @return string|mixed
* @throws HttpException
* @throws \Exception
*/
public function routeRequest($rewrite = false)
{
$routeNotAllowed = false;
@@ -236,18 +228,15 @@ class Router
$url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath();
$max = count($this->processedRoutes) - 1;
/* @var $route ILoadableRoute */
for ($i = $max; $i >= 0; $i--) {
foreach ($this->processedRoutes as $key => $route) {
$route = $this->processedRoutes[$i];
/* If the route matches */
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;
}
@@ -266,7 +255,7 @@ class Router
$rewriteUrl = $this->request->getRewriteUrl();
if ($rewriteUrl !== null && $rewriteUrl !== $url) {
unset($this->processedRoutes[$i]);
unset($this->processedRoutes[$key]);
$this->processedRoutes = array_values($this->processedRoutes);
return $this->routeRequest(true);
@@ -285,7 +274,7 @@ class Router
}
if ($routeNotAllowed === true) {
$message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUri()->getPath(), $this->request->getMethod());
$message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUri()->getPath(), $this->request->getMethod());
$this->handleException(new HttpException($message, 403));
}
@@ -315,12 +304,8 @@ class Router
{
$url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath();
$max = count($this->exceptionHandlers);
/* @var $handler IExceptionHandler */
for ($i = 0; $i < $max; $i++) {
$handler = $this->exceptionHandlers[$i];
foreach ($this->exceptionHandlers as $key => $handler) {
if (is_object($handler) === false) {
$handler = new $handler();
@@ -346,7 +331,7 @@ class Router
/* If the request has changed */
if ($rewriteUrl !== null && $rewriteUrl !== $url) {
unset($this->exceptionHandlers[$i]);
unset($this->exceptionHandlers[$key]);
$this->exceptionHandlers = array_values($this->exceptionHandlers);
return $this->routeRequest(true);
@@ -363,7 +348,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) {
@@ -385,12 +370,8 @@ class Router
*/
public function findRoute($name)
{
$max = count($this->processedRoutes) - 1;
/* @var $route ILoadableRoute */
for ($i = $max; $i >= 0; $i--) {
$route = $this->processedRoutes[$i];
foreach ($this->processedRoutes as $route) {
/* Check if the name matches with a name on the route. Should match either router alias or controller alias. */
if ($route->hasName($name)) {
@@ -489,15 +470,11 @@ class Router
/* Loop through all the routes to see if we can find a match */
$max = count($this->processedRoutes) - 1;
/* @var $route ILoadableRoute */
for ($i = $max; $i >= 0; $i--) {
$route = $this->processedRoutes[$i];
foreach ($this->processedRoutes as $route) {
/* Check if the route contains the name/alias */
if ($route->hasName($controller)) {
if ($route->hasName($controller) === true) {
return $route->findUrl($method, $parameters, $name) . $this->arrayToParams($getParams);
}
@@ -510,7 +487,7 @@ class Router
}
/* No result so we assume that someone is using a hardcoded url and join everything together. */
$url = trim(join('/', array_merge((array)$name, (array)$parameters)), '/');
$url = trim(implode('/', array_merge((array)$name, (array)$parameters)), '/');
return (($url === '') ? '/' : '/' . $url . '/') . $this->arrayToParams($getParams);
}
@@ -542,6 +519,16 @@ class Router
$this->bootManagers[] = $bootManager;
}
/**
* Get routes that has been processed.
*
* @return array
*/
public function getProcessedRoutes()
{
return $this->processedRoutes;
}
/**
* @return array
*/
+4 -5
View File
@@ -45,8 +45,7 @@ class SimpleRouter
/**
* Start/route request
*
* @throws HttpException
* @throws NotFoundHttpException
* @throws HttpException|NotFoundHttpException|\Exception
*/
public static function start()
{
@@ -368,7 +367,7 @@ class SimpleRouter
* @param string|null $name
* @param string|array|null $parameters
* @param array|null $getParams
* @throws \Exception
* @throws \InvalidArgumentException
* @return string
*/
public static function getUrl($name = null, $parameters = null, $getParams = null)
@@ -420,14 +419,14 @@ class SimpleRouter
* @param IRoute $route
* @return IRoute
*/
protected static function addDefaultNamespace(IRoute $route)
public static function addDefaultNamespace(IRoute $route)
{
if (static::$defaultNamespace !== null) {
$callback = $route->getCallback();
/* Only add default namespace on relative callbacks */
if ($callback === null || $callback[0] !== '\\') {
if ($callback === null || (is_string($callback) === true && $callback[0] !== '\\')) {
$namespace = static::$defaultNamespace;
+14
View File
@@ -125,6 +125,20 @@ class RouterRouteTest extends PHPUnit_Framework_TestCase
TestRouter::debug('/my/custom-path', 'get');
}
public function testParameterDefaultValue() {
$defaultVariable = null;
TestRouter::get('/my/{path?}', function($path = 'working') use(&$defaultVariable) {
$defaultVariable = $path;
});
TestRouter::debug('/my/');
$this->assertEquals('working', $defaultVariable);
}
public function testDefaultParameterRegex()
{
TestRouter::get('/my/{path}', 'DummyController@param', ['defaultParameterRegex' => '[\w\-]+']);