[Routing] support scheme requirement without redirectable dumped matcher

This commit is contained in:
Tobias Schultze 2018-02-25 03:12:15 +01:00 committed by Nicolas Grekas
parent 308e12cb59
commit f9b54c5ccd
21 changed files with 381 additions and 202 deletions

View File

@ -29,7 +29,6 @@ class PhpMatcherDumper extends MatcherDumper
{
private $expressionLanguage;
private $signalingException;
private $supportsRedirections;
/**
* @var ExpressionFunctionProviderInterface[]
@ -57,7 +56,7 @@ class PhpMatcherDumper extends MatcherDumper
// trailing slash support is only enabled if we know how to redirect the user
$interfaces = class_implements($options['base_class']);
$this->supportsRedirections = isset($interfaces[RedirectableUrlMatcherInterface::class]);
$supportsRedirections = isset($interfaces[RedirectableUrlMatcherInterface::class]);
return <<<EOF
<?php
@ -77,7 +76,7 @@ class {$options['class']} extends {$options['base_class']}
\$this->context = \$context;
}
{$this->generateMatchMethod()}
{$this->generateMatchMethod($supportsRedirections)}
}
EOF;
@ -91,7 +90,7 @@ EOF;
/**
* Generates the code for the match method implementing UrlMatcherInterface.
*/
private function generateMatchMethod(): string
private function generateMatchMethod(bool $supportsRedirections): string
{
// Group hosts by same-suffix, re-order when possible
$matchHost = false;
@ -111,7 +110,7 @@ EOF;
$code = <<<EOF
{
\$allow = array();
\$allow = \$allowSchemes = array();
\$pathinfo = rawurldecode(\$rawPathinfo);
\$context = \$this->context;
\$requestMethod = \$canonicalMethod = \$context->getMethod();
@ -124,25 +123,44 @@ $code
EOF;
if ($this->supportsRedirections) {
if ($supportsRedirections) {
return <<<'EOF'
public function match($pathinfo)
{
$allow = array();
if ($ret = $this->doMatch($pathinfo, $allow)) {
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo)) {
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array()): ?array
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
EOF
.$code."\n return null;\n }";
@ -238,9 +256,6 @@ EOF
}
if (!$route->getCondition()) {
if (!$this->supportsRedirections && $route->getSchemes()) {
throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
}
$default .= sprintf(
"%s => array(%s, %s, %s, %s),\n",
self::export($url),
@ -535,8 +550,8 @@ EOF;
} else {
$code = '';
}
if ($this->supportsRedirections) {
$code .= <<<EOF
$code .= <<<EOF
\$hasRequiredScheme = !\$requiredSchemes || isset(\$requiredSchemes[\$context->getScheme()]);
if (\$requiredMethods && !isset(\$requiredMethods[\$canonicalMethod]) && !isset(\$requiredMethods[\$requestMethod])) {
@ -546,28 +561,13 @@ EOF;
break;
}
if (!\$hasRequiredScheme) {
if ('GET' !== \$canonicalMethod) {
break;
}
return \$this->redirect(\$rawPathinfo, \$ret['_route'], key(\$requiredSchemes)) + \$ret;
}
return \$ret;
EOF;
} else {
$code .= <<<EOF
if (\$requiredMethods && !isset(\$requiredMethods[\$canonicalMethod]) && !isset(\$requiredMethods[\$requestMethod])) {
\$allow += \$requiredMethods;
\$allowSchemes += \$requiredSchemes;
break;
}
return \$ret;
EOF;
}
return $code;
}
@ -647,9 +647,6 @@ EOF;
}
if ($schemes = $route->getSchemes()) {
if (!$this->supportsRedirections) {
throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
}
$schemes = self::export(array_flip($schemes));
if ($methods) {
$code .= <<<EOF
@ -662,11 +659,8 @@ EOF;
goto $gotoname;
}
if (!\$hasRequiredScheme) {
if ('GET' !== \$canonicalMethod) {
goto $gotoname;
}
return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)) + \$ret;
\$allowSchemes += \$requiredSchemes;
goto $gotoname;
}
@ -675,11 +669,8 @@ EOF;
$code .= <<<EOF
\$requiredSchemes = $schemes;
if (!isset(\$requiredSchemes[\$context->getScheme()])) {
if ('GET' !== \$canonicalMethod) {
goto $gotoname;
}
return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)) + \$ret;
\$allowSchemes += \$requiredSchemes;
goto $gotoname;
}

View File

@ -11,8 +11,8 @@
namespace Symfony\Component\Routing\Matcher;
use Symfony\Component\Routing\Exception\ExceptionInterface;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Route;
/**
* @author Fabien Potencier <fabien@symfony.com>
@ -27,38 +27,38 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
try {
return parent::match($pathinfo);
} catch (ResourceNotFoundException $e) {
if ('/' === $pathinfo || !\in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if (!\in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
throw $e;
}
try {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
$ret = parent::match($pathinfo);
if ($this->allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(current($this->allowSchemes));
try {
$ret = parent::match($pathinfo);
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;
} catch (ResourceNotFoundException $e2) {
return $this->redirect($pathinfo, $ret['_route'] ?? null, $this->context->getScheme()) + $ret;
} catch (ExceptionInterface $e2) {
throw $e;
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' === $pathinfo) {
throw $e;
} else {
try {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
$ret = parent::match($pathinfo);
return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret;
} catch (ExceptionInterface $e2) {
if ($this->allowSchemes) {
goto redirect_scheme;
}
throw $e;
}
}
}
}
/**
* {@inheritdoc}
*/
protected function handleRouteRequirements($pathinfo, $name, Route $route)
{
// expression condition
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
return array(self::REQUIREMENT_MISMATCH, null);
}
// check HTTP scheme requirement
$scheme = $this->context->getScheme();
$schemes = $route->getSchemes();
if ($schemes && !$route->hasScheme($scheme)) {
return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes)));
}
return array(self::REQUIREMENT_MATCH, null);
}
}

View File

@ -33,7 +33,19 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
const ROUTE_MATCH = 2;
protected $context;
/**
* Collects HTTP methods that would be allowed for the request.
*/
protected $allow = array();
/**
* Collects URI schemes that would be allowed for the request.
*
* @internal
*/
protected $allowSchemes = array();
protected $routes;
protected $request;
protected $expressionLanguage;
@ -70,7 +82,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
*/
public function match($pathinfo)
{
$this->allow = array();
$this->allow = $this->allowSchemes = array();
if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
return $ret;
@ -141,7 +153,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
continue;
}
// check HTTP method requirement
$hasRequiredScheme = !$route->getSchemes() || $route->hasScheme($this->context->getScheme());
if ($requiredMethods = $route->getMethods()) {
// HEAD and GET are equivalent as per RFC
if ('HEAD' === $method = $this->context->getMethod()) {
@ -149,7 +161,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
}
if (!in_array($method, $requiredMethods)) {
if (self::REQUIREMENT_MATCH === $status[0]) {
if ($hasRequiredScheme) {
$this->allow = array_merge($this->allow, $requiredMethods);
}
@ -157,6 +169,12 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
}
}
if (!$hasRequiredScheme) {
$this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());
continue;
}
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : array()));
}
}
@ -197,11 +215,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
return array(self::REQUIREMENT_MISMATCH, null);
}
// check HTTP scheme requirement
$scheme = $this->context->getScheme();
$status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
return array($status, null);
return array(self::REQUIREMENT_MATCH, null);
}
/**

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -64,8 +64,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
@ -209,8 +216,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -2799,8 +2799,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,23 +17,42 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
public function match($pathinfo)
{
$allow = array();
if ($ret = $this->doMatch($pathinfo, $allow)) {
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo)) {
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array()): ?array
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -113,11 +132,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -69,8 +69,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();

View File

@ -17,23 +17,42 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
public function match($pathinfo)
{
$allow = array();
if ($ret = $this->doMatch($pathinfo, $allow)) {
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo)) {
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array()): ?array
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -90,11 +109,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
@ -245,11 +261,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -43,8 +43,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
@ -74,8 +81,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -60,8 +60,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,23 +17,42 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
public function match($pathinfo)
{
$allow = array();
if ($ret = $this->doMatch($pathinfo, $allow)) {
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo)) {
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array()): ?array
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -72,11 +91,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
@ -115,11 +131,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -44,8 +44,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
list($ret, $requiredHost, $requiredMethods, $requiredSchemes) = $routes[$pathinfo];
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}
@ -93,8 +100,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,23 +17,42 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
public function match($pathinfo)
{
$allow = array();
if ($ret = $this->doMatch($pathinfo, $allow)) {
$allow = $allowSchemes = array();
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $ret;
}
if ('/' !== $pathinfo && in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
if ($allow) {
throw new MethodNotAllowedException(array_keys($allow));
}
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'), true)) {
// no-op
} elseif ($allowSchemes) {
redirect_scheme:
$scheme = $this->context->getScheme();
$this->context->setScheme(key($allowSchemes));
try {
if ($ret = $this->doMatch($pathinfo)) {
return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret;
}
} finally {
$this->context->setScheme($scheme);
}
} elseif ('/' !== $pathinfo) {
$pathinfo = '/' !== $pathinfo[-1] ? $pathinfo.'/' : substr($pathinfo, 0, -1);
if ($ret = $this->doMatch($pathinfo)) {
if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) {
return $this->redirect($pathinfo, $ret['_route']) + $ret;
}
if ($allowSchemes) {
goto redirect_scheme;
}
}
throw $allow ? new MethodNotAllowedException(array_keys($allow)) : new ResourceNotFoundException();
throw new ResourceNotFoundException();
}
private function doMatch(string $rawPathinfo, array &$allow = array()): ?array
private function doMatch(string $rawPathinfo, array &$allow = array(), array &$allowSchemes = array()): ?array
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -68,11 +87,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;
@ -127,11 +143,8 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
break;
}
if (!$hasRequiredScheme) {
if ('GET' !== $canonicalMethod) {
break;
}
return $this->redirect($rawPathinfo, $ret['_route'], key($requiredSchemes)) + $ret;
$allowSchemes += $requiredSchemes;
break;
}
return $ret;

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();
@ -57,8 +57,15 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
}
}
$hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]);
if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) {
$allow += $requiredMethods;
if ($hasRequiredScheme) {
$allow += $requiredMethods;
}
break;
}
if (!$hasRequiredScheme) {
$allowSchemes += $requiredSchemes;
break;
}

View File

@ -17,7 +17,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
public function match($rawPathinfo)
{
$allow = array();
$allow = $allowSchemes = array();
$pathinfo = rawurldecode($rawPathinfo);
$context = $this->context;
$requestMethod = $canonicalMethod = $context->getMethod();

View File

@ -17,24 +17,6 @@ use Symfony\Component\Routing\RequestContext;
class DumpedUrlMatcherTest extends UrlMatcherTest
{
/**
* @expectedException \LogicException
* @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.
*/
public function testSchemeRequirement()
{
parent::testSchemeRequirement();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.
*/
public function testSchemeAndMethodMismatch()
{
parent::testSchemeRequirement();
}
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
static $i = 0;

View File

@ -46,24 +46,6 @@ class PhpMatcherDumperTest extends TestCase
@unlink($this->dumpPath);
}
/**
* @expectedException \LogicException
*/
public function testDumpWhenSchemeIsUsedWithoutAProperDumper()
{
$collection = new RouteCollection();
$collection->add('secure', new Route(
'/secure',
array(),
array(),
array(),
'',
array('https')
));
$dumper = new PhpMatcherDumper($collection);
$dumper->dump();
}
public function testRedirectPreservesUrlEncoding()
{
$collection = new RouteCollection();

View File

@ -17,7 +17,7 @@ use Symfony\Component\Routing\RequestContext;
class RedirectableUrlMatcherTest extends UrlMatcherTest
{
public function testRedirectWhenNoSlash()
public function testMissingTrailingSlash()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo/'));
@ -27,7 +27,7 @@ class RedirectableUrlMatcherTest extends UrlMatcherTest
$matcher->match('/foo');
}
public function testRedirectWhenSlash()
public function testExtraTrailingSlash()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo'));
@ -127,6 +127,16 @@ class RedirectableUrlMatcherTest extends UrlMatcherTest
$this->assertSame(array('_route' => 'foo'), $matcher->match('/foo'));
}
public function testMissingTrailingSlashAndScheme()
{
$coll = new RouteCollection();
$coll->add('foo', (new Route('/foo/'))->setSchemes(array('https')));
$matcher = $this->getUrlMatcher($coll);
$matcher->expects($this->once())->method('redirect')->with('/foo/', 'foo', 'https')->will($this->returnValue(array()));
$matcher->match('/foo');
}
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
return $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($routes, $context ?: new RequestContext()));

View File

@ -325,6 +325,58 @@ class UrlMatcherTest extends TestCase
$matcher->match('/do.t.html');
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testMissingTrailingSlash()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo/'));
$matcher = $this->getUrlMatcher($coll);
$matcher->match('/foo');
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testExtraTrailingSlash()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo'));
$matcher = $this->getUrlMatcher($coll);
$matcher->match('/foo/');
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testMissingTrailingSlashForNonSafeMethod()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo/'));
$context = new RequestContext();
$context->setMethod('POST');
$matcher = $this->getUrlMatcher($coll, $context);
$matcher->match('/foo');
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testExtraTrailingSlashForNonSafeMethod()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo'));
$context = new RequestContext();
$context->setMethod('POST');
$matcher = $this->getUrlMatcher($coll, $context);
$matcher->match('/foo/');
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
@ -336,6 +388,29 @@ class UrlMatcherTest extends TestCase
$matcher->match('/foo');
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testSchemeRequirementForNonSafeMethod()
{
$coll = new RouteCollection();
$coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https')));
$context = new RequestContext();
$context->setMethod('POST');
$matcher = $this->getUrlMatcher($coll, $context);
$matcher->match('/foo');
}
public function testSamePathWithDifferentScheme()
{
$coll = new RouteCollection();
$coll->add('https_route', new Route('/', array(), array(), array(), '', array('https')));
$coll->add('http_route', new Route('/', array(), array(), array(), '', array('http')));
$matcher = $this->getUrlMatcher($coll);
$this->assertEquals(array('_route' => 'http_route'), $matcher->match('/'));
}
/**
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/