bug #26100 [Routing] Throw 405 instead of 404 when redirect is not possible (nicolas-grekas)

This PR was merged into the 2.7 branch.

Discussion
----------

[Routing] Throw 405 instead of 404 when redirect is not possible

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Finishes #25962.

Commits
-------

92842814f6 [Routing] Throw 405 instead of 404 when redirect is not possible
This commit is contained in:
Fabien Potencier 2018-02-12 18:42:00 +01:00
commit aa3a04ad28
3 changed files with 61 additions and 31 deletions

View File

@ -217,7 +217,7 @@ EOF;
$methods[] = 'HEAD';
}
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('GET', $methods));
if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) {
@ -258,6 +258,42 @@ EOF;
EOF;
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);
if ($hasTrailingSlash) {
$code .= <<<EOF
if ('/' === substr(\$pathinfo, -1)) {
// no-op
} elseif (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) {
\$allow[] = 'GET';
goto $gotoname;
} else {
return \$this->redirect(\$rawPathinfo.'/', '$name');
}
EOF;
}
if ($schemes = $route->getSchemes()) {
if (!$supportsRedirections) {
throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
}
$schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
$code .= <<<EOF
\$requiredSchemes = $schemes;
if (!isset(\$requiredSchemes[\$this->context->getScheme()])) {
if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) {
\$allow[] = 'GET';
goto $gotoname;
}
return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes));
}
EOF;
}
if ($methods) {
if (1 === count($methods)) {
$code .= <<<EOF
@ -281,35 +317,6 @@ EOF;
}
}
if ($hasTrailingSlash) {
$code .= <<<EOF
if ('/' === substr(\$pathinfo, -1)) {
// no-op
} elseif (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) {
goto $gotoname;
} else {
return \$this->redirect(\$rawPathinfo.'/', '$name');
}
EOF;
}
if ($schemes = $route->getSchemes()) {
if (!$supportsRedirections) {
throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.');
}
$schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
$code .= <<<EOF
\$requiredSchemes = $schemes;
if (!isset(\$requiredSchemes[\$this->context->getScheme()])) {
return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes));
}
EOF;
}
// optimize parameters array
if ($matches || $hostMatches) {
$vars = array();
@ -333,7 +340,7 @@ EOF;
}
$code .= " }\n";
if ($methods || $hasTrailingSlash) {
if ($hasTrailingSlash || $schemes || $methods) {
$code .= " $gotoname:\n";
}

View File

@ -69,6 +69,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif (!in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
$allow[] = 'GET';
goto not_baz3;
} else {
return $this->redirect($rawPathinfo.'/', 'baz3');
@ -85,6 +86,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif (!in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
$allow[] = 'GET';
goto not_baz4;
} else {
return $this->redirect($rawPathinfo.'/', 'baz4');
@ -183,6 +185,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if ('/' === substr($pathinfo, -1)) {
// no-op
} elseif (!in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
$allow[] = 'GET';
goto not_hey;
} else {
return $this->redirect($rawPathinfo.'/', 'hey');
@ -333,21 +336,33 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
if ('/secure' === $pathinfo) {
$requiredSchemes = array ( 'https' => 0,);
if (!isset($requiredSchemes[$this->context->getScheme()])) {
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
$allow[] = 'GET';
goto not_secure;
}
return $this->redirect($rawPathinfo, 'secure', key($requiredSchemes));
}
return array('_route' => 'secure');
}
not_secure:
// nonsecure
if ('/nonsecure' === $pathinfo) {
$requiredSchemes = array ( 'http' => 0,);
if (!isset($requiredSchemes[$this->context->getScheme()])) {
if (!in_array($this->context->getMethod(), array('HEAD', 'GET'))) {
$allow[] = 'GET';
goto not_nonsecure;
}
return $this->redirect($rawPathinfo, 'nonsecure', key($requiredSchemes));
}
return array('_route' => 'nonsecure');
}
not_nonsecure:
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
}

View File

@ -19,6 +19,14 @@ use Symfony\Component\Routing\RequestContext;
class DumpedRedirectableUrlMatcherTest extends RedirectableUrlMatcherTest
{
/**
* @expectedException \Symfony\Component\Routing\Exception\MethodNotAllowedException
*/
public function testRedirectWhenNoSlashForNonSafeMethod()
{
parent::testRedirectWhenNoSlashForNonSafeMethod();
}
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)
{
static $i = 0;