Skip to content

Commit

Permalink
minor #51986 [Security] Do not match request twice in HttpUtils (To…
Browse files Browse the repository at this point in the history
…flar)

This PR was merged into the 5.4 branch.

Discussion
----------

[Security] Do not match request twice in `HttpUtils`

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       |
| License       | MIT

Found a pretty heavy performance issue in [Contao](https://contao.org) (thank you, Blackfire for sponsoring my Open Source license ❤️). It's probably not very apparent if you only use Symfony core components (due to fast regex matching) but as soon as you use dynamic router matching using e.g. [Symfony CMF Routing](https://github.com/symfony-cmf/Routing) with database hits like we do, you may encounter this issue.

The problem is that currently, `HttpUtils::checkRequestPath()` always matches a given `Request`, even if that has already been matched before. This is the **default** case in any Symfony application because

* In the `kernel.request` event stack, the `RouterListener` is called first. Route matching happens here.
* Later, the `FirewallListener` is called. In its stack, it calls `LogoutListener::supports()` which **always** calls `requiresLogout()` and thus triggers an additional match via `HttpUtils::checkRequestPath()` for every single request (https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php#L142).

So if route matching has already happened before, we should not match again.

Commits
-------

ccacdf8 Do not match request twice in HttpUtils
  • Loading branch information
fabpot committed Oct 13, 2023
2 parents e7d45da + ccacdf8 commit 53e5e19
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Symfony/Component/Security/Http/HttpUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public function createRequest(Request $request, string $path)
public function checkRequestPath(Request $request, string $path)
{
if ('/' !== $path[0]) {
// Shortcut if request has already been matched before
if ($request->attributes->has('_route')) {
return $path === $request->attributes->get('_route');
}

try {
// matching a request is more powerful than matching a URL path + context, so try that first
if ($this->urlMatcher instanceof RequestMatcherInterface) {
Expand Down
16 changes: 16 additions & 0 deletions src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,22 @@ public function testCheckRequestPathWithUrlMatcherLoadingException()
$utils->checkRequestPath($this->getRequest(), 'foobar');
}

public function testCheckRequestPathWithRequestAlreadyMatchedBefore()
{
$urlMatcher = $this->createMock(RequestMatcherInterface::class);
$urlMatcher
->expects($this->never())
->method('matchRequest')
;

$request = $this->getRequest();
$request->attributes->set('_route', 'route_name');

$utils = new HttpUtils(null, $urlMatcher);
$this->assertTrue($utils->checkRequestPath($request, 'route_name'));
$this->assertFalse($utils->checkRequestPath($request, 'foobar'));
}

public function testCheckPathWithoutRouteParam()
{
$urlMatcher = $this->createMock(UrlMatcherInterface::class);
Expand Down

0 comments on commit 53e5e19

Please sign in to comment.