Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a15c14e

Browse filesBrowse files
[HttpKernel] fix session tracking in surrogate master requests
1 parent 143bdfc commit a15c14e
Copy full SHA for a15c14e

File tree

5 files changed

+73
-13
lines changed
Filter options

5 files changed

+73
-13
lines changed

‎src/Symfony/Component/HttpFoundation/Session/Session.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Session/Session.php
+14-5Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
2929
private $flashName;
3030
private $attributeName;
3131
private $data = array();
32-
private $hasBeenStarted;
32+
private $usageIndex = 0;
3333

3434
/**
3535
* @param SessionStorageInterface $storage A SessionStorageInterface instance
@@ -54,6 +54,8 @@ public function __construct(SessionStorageInterface $storage = null, AttributeBa
5454
*/
5555
public function start()
5656
{
57+
++$this->usageIndex;
58+
5759
return $this->storage->start();
5860
}
5961

@@ -142,13 +144,13 @@ public function count()
142144
}
143145

144146
/**
145-
* @return bool
147+
* @return int
146148
*
147149
* @internal
148150
*/
149-
public function hasBeenStarted()
151+
public function getUsageIndex()
150152
{
151-
return $this->hasBeenStarted;
153+
return $this->usageIndex;
152154
}
153155

154156
/**
@@ -158,6 +160,7 @@ public function hasBeenStarted()
158160
*/
159161
public function isEmpty()
160162
{
163+
++$this->usageIndex;
161164
foreach ($this->data as &$data) {
162165
if (!empty($data)) {
163166
return false;
@@ -182,6 +185,8 @@ public function invalidate($lifetime = null)
182185
*/
183186
public function migrate($destroy = false, $lifetime = null)
184187
{
188+
++$this->usageIndex;
189+
185190
return $this->storage->regenerate($destroy, $lifetime);
186191
}
187192

@@ -190,6 +195,8 @@ public function migrate($destroy = false, $lifetime = null)
190195
*/
191196
public function save()
192197
{
198+
++$this->usageIndex;
199+
193200
$this->storage->save();
194201
}
195202

@@ -230,6 +237,8 @@ public function setName($name)
230237
*/
231238
public function getMetadataBag()
232239
{
240+
++$this->usageIndex;
241+
233242
return $this->storage->getMetadataBag();
234243
}
235244

@@ -238,7 +247,7 @@ public function getMetadataBag()
238247
*/
239248
public function registerBag(SessionBagInterface $bag)
240249
{
241-
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->hasBeenStarted));
250+
$this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex));
242251
}
243252

244253
/**

‎src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php
+10-4Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,22 @@ final class SessionBagProxy implements SessionBagInterface
2020
{
2121
private $bag;
2222
private $data;
23-
private $hasBeenStarted;
23+
private $usageIndex;
2424

25-
public function __construct(SessionBagInterface $bag, array &$data, &$hasBeenStarted)
25+
public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex)
2626
{
2727
$this->bag = $bag;
2828
$this->data = &$data;
29-
$this->hasBeenStarted = &$hasBeenStarted;
29+
$this->usageIndex = &$usageIndex;
3030
}
3131

3232
/**
3333
* @return SessionBagInterface
3434
*/
3535
public function getBag()
3636
{
37+
++$this->usageIndex;
38+
3739
return $this->bag;
3840
}
3941

@@ -42,6 +44,8 @@ public function getBag()
4244
*/
4345
public function isEmpty()
4446
{
47+
++$this->usageIndex;
48+
4549
return empty($this->data[$this->bag->getStorageKey()]);
4650
}
4751

@@ -58,7 +62,7 @@ public function getName()
5862
*/
5963
public function initialize(array &$array)
6064
{
61-
$this->hasBeenStarted = true;
65+
++$this->usageIndex;
6266
$this->data[$this->bag->getStorageKey()] = &$array;
6367

6468
$this->bag->initialize($array);
@@ -77,6 +81,8 @@ public function getStorageKey()
7781
*/
7882
public function clear()
7983
{
84+
++$this->usageIndex;
85+
8086
return $this->bag->clear();
8187
}
8288
}

‎src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php
+13-1Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
*/
2626
abstract class AbstractSessionListener implements EventSubscriberInterface
2727
{
28+
private $sessionUsageStack = array();
29+
2830
public function onKernelRequest(GetResponseEvent $event)
2931
{
3032
if (!$event->isMasterRequest()) {
@@ -33,6 +35,7 @@ public function onKernelRequest(GetResponseEvent $event)
3335

3436
$request = $event->getRequest();
3537
$session = $this->getSession();
38+
$this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : null;
3639
if (null === $session || $request->hasSession()) {
3740
return;
3841
}
@@ -50,20 +53,29 @@ public function onKernelResponse(FilterResponseEvent $event)
5053
return;
5154
}
5255

53-
if ($session->isStarted() || ($session instanceof Session && $session->hasBeenStarted())) {
56+
if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) {
5457
$event->getResponse()
5558
->setPrivate()
5659
->setMaxAge(0)
5760
->headers->addCacheControlDirective('must-revalidate');
5861
}
5962
}
6063

64+
/**
65+
* @internal
66+
*/
67+
public function onFinishRequest()
68+
{
69+
array_pop($this->sessionUsageStack);
70+
}
71+
6172
public static function getSubscribedEvents()
6273
{
6374
return array(
6475
KernelEvents::REQUEST => array('onKernelRequest', 128),
6576
// low priority to come after regular response listeners, same as SaveSessionListener
6677
KernelEvents::RESPONSE => array('onKernelResponse', -1000),
78+
KernelEvents::FINISH_REQUEST => array('onFinishRequest'),
6779
);
6880
}
6981

‎src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php
+35-2Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public function testSessionIsSet()
5858
public function testResponseIsPrivate()
5959
{
6060
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
61-
$session->expects($this->once())->method('isStarted')->willReturn(false);
62-
$session->expects($this->once())->method('hasBeenStarted')->willReturn(true);
61+
$session->expects($this->exactly(2))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1));
6362

6463
$container = new Container();
6564
$container->set('session', $session);
@@ -76,4 +75,38 @@ public function testResponseIsPrivate()
7675
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
7776
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
7877
}
78+
79+
public function testSurrogateMasterRequestIsPublic()
80+
{
81+
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
82+
$session->expects($this->exactly(4))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1, 1, 1));
83+
84+
$container = new Container();
85+
$container->set('session', $session);
86+
87+
$listener = new SessionListener($container);
88+
$kernel = $this->getMockBuilder(HttpKernelInterface::class)->disableOriginalConstructor()->getMock();
89+
90+
$request = new Request();
91+
$response = new Response();
92+
$response->setCache(array('public' => true, 'max_age' => '30'));
93+
$listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST));
94+
$this->assertTrue($request->hasSession());
95+
96+
$subRequest = clone $request;
97+
$this->assertSame($request->getSession(), $subRequest->getSession());
98+
$listener->onKernelRequest(new GetResponseEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST));
99+
$listener->onKernelResponse(new FilterResponseEvent($kernel, $subRequest, HttpKernelInterface::MASTER_REQUEST, $response));
100+
$listener->onFinishRequest();
101+
102+
$this->assertFalse($response->headers->hasCacheControlDirective('private'));
103+
$this->assertFalse($response->headers->hasCacheControlDirective('must-revalidate'));
104+
$this->assertSame('30', $response->headers->getCacheControlDirective('max-age'));
105+
106+
$listener->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response));
107+
108+
$this->assertTrue($response->headers->hasCacheControlDirective('private'));
109+
$this->assertTrue($response->headers->hasCacheControlDirective('must-revalidate'));
110+
$this->assertSame('0', $response->headers->getCacheControlDirective('max-age'));
111+
}
79112
}

‎src/Symfony/Component/HttpKernel/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": "^5.5.9|>=7.0.8",
2020
"symfony/event-dispatcher": "~2.8|~3.0|~4.0",
21-
"symfony/http-foundation": "^3.4.4|^4.0.4",
21+
"symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1",
2222
"symfony/debug": "~2.8|~3.0|~4.0",
2323
"symfony/polyfill-ctype": "~1.8",
2424
"psr/log": "~1.0"

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.