From 7c0ce368ab383a15ccf2ad77e18d2e1b33d5949e Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sat, 22 Oct 2016 10:36:02 +0000 Subject: [PATCH 1/2] poc --- .../HttpFoundation/RequestMatcher.php | 7 ++ .../RequestMatcher/ChainRequestMatcher.php | 44 +++++++++ .../RequestMatcher/PathRequestMatcher.php | 44 +++++++++ .../RequestMatcher/RequestMatcher.php | 94 +++++++++++++++++++ .../RequestMatcherInterface.php | 31 ++++++ .../RequestMatcherInterface.php | 14 +-- 6 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/RequestMatcher/ChainRequestMatcher.php create mode 100644 src/Symfony/Component/HttpFoundation/RequestMatcher/PathRequestMatcher.php create mode 100644 src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php create mode 100644 src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcherInterface.php diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher.php index ca094ca16293a..386005279b362 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher.php @@ -11,6 +11,11 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcher\RequestMatcher as BaseRequestMatcher; + +@trigger_error('The '.RequestMatcher::class.' class is deprecated since version 3.2 and will be removed in 4.0. Use the '.BaseRequestMatcher::class.' class instead.', E_USER_DEPRECATED); + /** * RequestMatcher compares a pre-defined set of checks against a Request instance. * @@ -96,6 +101,8 @@ public function matchHost($regexp) */ public function matchPath($regexp) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0. Use the '.PathRequestMatcher::class.' class instead.', E_USER_DEPRECATED); + $this->path = $regexp; } diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/ChainRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/ChainRequestMatcher.php new file mode 100644 index 0000000000000..983b180e54ae4 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/ChainRequestMatcher.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Roland Franssen + */ +class ChainRequestMatcher implements RequestMatcherInterface +{ + private $matchers; + + /** + * @param RequestMatcherInterface[] $matchers + */ + public function __construct(array $matchers) + { + $this->matchers = $matchers; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + foreach ($this->matchers as $matcher) { + if (!$matcher->matches($request)) { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/PathRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/PathRequestMatcher.php new file mode 100644 index 0000000000000..840ce5a5bba8e --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/PathRequestMatcher.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Fabien Potencier + * @author Roland Franssen + */ +class PathRequestMatcher implements RequestMatcherInterface +{ + private $path; + private $isRegex; + + /** + * @param string $path + * @param bool $isRegex + */ + public function __construct($path, $isRegex = false) + { + $this->path = $path; + $this->isRegex = $isRegex; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + return $this->isRegex + ? (bool) preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo())) + : $this->path === rawurldecode($request->getPathInfo()); + } +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php new file mode 100644 index 0000000000000..a0b582eb4dee8 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + * @author Roland Franssen + */ +class RequestMatcher extends ChainRequestMatcher +{ + private $host; + private $methods; + private $ips; + private $attributes; + private $schemes; + + /** + * @param string|null $path + * @param string|null $host + * @param string[] $methods + * @param string[] $ips + * @param array $attributes + * @param string[] $schemes + */ + public function __construct($path = null, $host = null, array $methods = array(), array $ips = array(), array $attributes = array(), array $schemes = array()) + { + $matchers = array(); + if (null !== $path) { + $matchers[] = new PathRequestMatcher($path, true); + } + + parent::__construct($matchers); + + // @todo + $this->host = $host; + $this->methods = $methods; + $this->ips = $ips; + $this->attributes = $attributes; + $this->schemes = $schemes; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if (!parent::matches($request)) { + return false; + } + + if ($this->schemes && !in_array($request->getScheme(), $this->schemes)) { + return false; + } + + if ($this->methods && !in_array($request->getMethod(), $this->methods)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + return false; + } + } + + if (!parent::matches($request)) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return count($this->ips) === 0; + } +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcherInterface.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcherInterface.php new file mode 100644 index 0000000000000..ba669381834b1 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @param Request $request The request to check for a match + * + * @return bool true if the request matches, false otherwise + */ + public function matches(Request $request); +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcherInterface.php b/src/Symfony/Component/HttpFoundation/RequestMatcherInterface.php index 066e7e8bf1dee..a80ffac60d0d8 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcherInterface.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcherInterface.php @@ -11,19 +11,15 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\RequestMatcher\RequestMatcherInterface as BaseRequestMatcherInterface; + +@trigger_error('The '.RequestMatcherInterface::class.' class is deprecated since version 3.2 and will be removed in 4.0. Use the '.BaseRequestMatcherInterface::class.' class instead.', E_USER_DEPRECATED); + /** * RequestMatcherInterface is an interface for strategies to match a Request. * * @author Fabien Potencier */ -interface RequestMatcherInterface +interface RequestMatcherInterface extends BaseRequestMatcherInterface { - /** - * Decides whether the rule(s) implemented by the strategy matches the supplied request. - * - * @param Request $request The request to check for a match - * - * @return bool true if the request matches, false otherwise - */ - public function matches(Request $request); } From 0571193079ef5a7a9a6a4d96f7659045ba0f45f4 Mon Sep 17 00:00:00 2001 From: Roland Franssen Date: Sat, 22 Oct 2016 11:28:00 +0000 Subject: [PATCH 2/2] added standalone expression request matcher --- .../ExpressionRequestMatcher.php | 45 +++++++++++++++++++ .../RequestMatcher/RequestMatcher.php | 4 -- 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/RequestMatcher/ExpressionRequestMatcher.php diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/ExpressionRequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/ExpressionRequestMatcher.php new file mode 100644 index 0000000000000..cb293ef7f6531 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/ExpressionRequestMatcher.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RequestMatcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + * @author Roland Franssen + */ +class ExpressionRequestMatcher implements RequestMatcherInterface +{ + private $language; + private $expression; + + public function __construct(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + return (bool) $this->language->evaluate($this->expression, array( + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + )); + } +} diff --git a/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php b/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php index a0b582eb4dee8..06ed0e8136d1e 100644 --- a/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php +++ b/src/Symfony/Component/HttpFoundation/RequestMatcher/RequestMatcher.php @@ -57,10 +57,6 @@ public function __construct($path = null, $host = null, array $methods = array() */ public function matches(Request $request) { - if (!parent::matches($request)) { - return false; - } - if ($this->schemes && !in_array($request->getScheme(), $this->schemes)) { return false; }