Skip to content

Commit f7b5cda

Browse files
committed
Fixed cookie and session handling issues
With this it is necessary to replace the NativeSessionStorage to not mix up strong and regular session ids. Fixed #68 Fixed also a issue where touch() triggers a restart although the actual content of the php file has not been changed.
1 parent 0fdee10 commit f7b5cda

File tree

3 files changed

+133
-10
lines changed

3 files changed

+133
-10
lines changed

Bootstraps/Symfony.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPPM\Bootstraps;
44

5+
use PHPPM\Symfony\StrongerNativeSessionStorage;
56
use PHPPM\Utils;
67
use Symfony\Component\HttpFoundation\Request;
78

@@ -52,9 +53,35 @@ public function getApplication()
5253
require './vendor/autoload.php';
5354
}
5455

56+
//since we need to change some services, we need to manually change some services
5557
$app = new \AppKernel($this->appenv, $this->debug);
56-
$app->loadClassCache();
57-
$app->boot();
58+
59+
//we need to change some services, before the boot, because they would otherwise
60+
//be instantiated and passed to other classes which makes it impossible to replace them.
61+
Utils::bindAndCall(function() use ($app) {
62+
// init bundles
63+
$app->initializeBundles();
64+
65+
// init container
66+
$app->initializeContainer();
67+
}, $app);
68+
69+
//now we can modify the container
70+
$nativeStorage = new StrongerNativeSessionStorage(
71+
$app->getContainer()->getParameter('session.storage.options'),
72+
$app->getContainer()->has('session.handler') ? $app->getContainer()->get('session.handler'): null,
73+
$app->getContainer()->get('session.storage.metadata_bag')
74+
);
75+
$app->getContainer()->set('session.storage.native', $nativeStorage);
76+
77+
Utils::bindAndCall(function() use ($app) {
78+
foreach ($app->getBundles() as $bundle) {
79+
$bundle->setContainer($app->container);
80+
$bundle->boot();
81+
}
82+
83+
$app->booted = true;
84+
}, $app);
5885

5986
//warm up
6087
$request = new Request();

Bridges/HttpKernel.php

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPPM\Bootstraps\BootstrapInterface;
77
use PHPPM\Bootstraps\HooksInterface;
88
use PHPPM\React\HttpResponse;
9+
use PHPPM\Utils;
910
use React\Http\Request as ReactRequest;
1011
use Symfony\Component\HttpFoundation\Cookie;
1112
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
@@ -65,6 +66,8 @@ public function getStaticDirectory()
6566
*
6667
* @param ReactRequest $request
6768
* @param HttpResponse $response
69+
*
70+
* @throws \Exception
6871
*/
6972
public function onRequest(ReactRequest $request, HttpResponse $response)
7073
{
@@ -111,18 +114,35 @@ public function onRequest(ReactRequest $request, HttpResponse $response)
111114
protected function mapRequest(ReactRequest $reactRequest)
112115
{
113116
$method = $reactRequest->getMethod();
114-
$headers = array_change_key_case($reactRequest->getHeaders());
117+
$headers = $reactRequest->getHeaders();
115118
$query = $reactRequest->getQuery();
116119

117-
$cookies = array();
118-
if (isset($headers['Cookie'])) {
119-
$headersCookie = explode(';', $headers['Cookie']);
120+
$cookies = [];
121+
$_COOKIE = [];
122+
123+
$sessionCookieSet = false;
124+
125+
if (isset($headers['Cookie']) || isset($headers['cookie'])) {
126+
$headersCookie = explode(';', isset($headers['Cookie']) ? $headers['Cookie'] : $headers['cookie']);
120127
foreach ($headersCookie as $cookie) {
121128
list($name, $value) = explode('=', trim($cookie));
122129
$cookies[$name] = $value;
130+
$_COOKIE[$name] = $value;
131+
132+
if ($name === session_name()) {
133+
session_id($value);
134+
$sessionCookieSet = true;
135+
}
123136
}
124137
}
125138

139+
if (!$sessionCookieSet && session_id()) {
140+
//session id already set from the last round but not got from the cookie header,
141+
//so generate a new one, since php is not doing it automatically with session_start() if session
142+
//has already been started.
143+
session_id(Utils::generateSessionId());
144+
}
145+
126146
$files = $reactRequest->getFiles();
127147
$post = $reactRequest->getPost();
128148

@@ -144,17 +164,46 @@ protected function mapRequest(ReactRequest $reactRequest)
144164
*/
145165
protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syResponse)
146166
{
167+
//end active session
168+
if (PHP_SESSION_ACTIVE === session_status()) {
169+
session_write_close();
170+
session_unset(); //reset $_SESSION
171+
}
172+
147173
$content = $syResponse->getContent();
148174

149-
$headers = $syResponse->headers->allPreserveCase();
175+
$nativeHeaders = [];
176+
177+
foreach (headers_list() as $header) {
178+
if (false !== $pos = strpos($header, ':')) {
179+
$name = substr($header, 0, $pos);
180+
$value = trim(substr($header, $pos + 1));
181+
182+
if (isset($nativeHeaders[$name])) {
183+
if (!is_array($nativeHeaders[$name])) {
184+
$nativeHeaders[$name] = [$nativeHeaders[$name]];
185+
}
186+
187+
$nativeHeaders[$name][] = $value;
188+
} else {
189+
$nativeHeaders[$name] = $value;
190+
}
191+
}
192+
}
193+
194+
//after reading all headers we need to reset it, so next request
195+
//operates on a clean header.
196+
header_remove();
197+
198+
$headers = array_merge($nativeHeaders, $syResponse->headers->allPreserveCase());
150199
$cookies = [];
151200

152201
/** @var Cookie $cookie */
153202
foreach ($syResponse->headers->getCookies() as $cookie) {
154203
$cookieHeader = sprintf('%s=%s', $cookie->getName(), $cookie->getValue());
155204

156205
if ($cookie->getPath()) {
157-
$cookieHeader .= '; Path=' . $cookie->getPath();
206+
$cookieHeader .= '; Path=' . urlencode($cookie->getPath());
158207
}
159208
if ($cookie->getDomain()) {
160209
$cookieHeader .= '; Domain=' . $cookie->getDomain();
@@ -174,7 +223,11 @@ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syR
174223
$cookies[] = $cookieHeader;
175224
}
176225

177-
$headers['Set-Cookie'] = $cookies;
226+
if (isset($headers['Set-Cookie'])) {
227+
$headers['Set-Cookie'] = array_merge((array)$headers['Set-Cookie'], $cookies);
228+
} else {
229+
$headers['Set-Cookie'] = $cookies;
230+
}
178231

179232
$reactResponse->writeHead($syResponse->getStatusCode(), $headers);
180233

@@ -184,7 +237,6 @@ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syR
184237
}
185238

186239
$reactResponse->end($stdOut . $content);
187-
188240
}
189241

190242
/**
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace PHPPM\Symfony;
4+
5+
/**
6+
* Since PHP-PM needs to generate its session ids on its own due to the fact that session_destroy()
7+
* does not reset session_id() nor does it generate a new one for a new session, we need
8+
* to overwrite Symfonys NativeSessionStorage that uses session_regenerate_id() which generates
9+
* the weaker session ids from PHP. So we need to overwrite the method regenerate(), to set a better
10+
* session id afterwards.
11+
*/
12+
class StrongerNativeSessionStorage extends \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage
13+
{
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public function regenerate($destroy = false, $lifetime = null)
18+
{
19+
//since session_regenerate_id also places a setcookie call, we need to deactivate this, to not have
20+
//two Set-Cookie headers
21+
ini_set('session.use_cookies', 0);
22+
if ($isRegenerated = parent::regenerate($destroy, $lifetime)) {
23+
$params = session_get_cookie_params();
24+
25+
session_id(\PHPPM\Utils::generateSessionId());
26+
error_log('StrongerNativeSessionStorage::regenerate ' . $params['lifetime'] . ' = ' . session_id());
27+
28+
setcookie(
29+
session_name(),
30+
session_id(),
31+
$params['lifetime'] ? time() + $params['lifetime'] : null,
32+
$params['path'],
33+
$params['domain'],
34+
$params['secure'],
35+
$params['httponly']
36+
);
37+
}
38+
ini_set('session.use_cookies', 1);
39+
40+
return $isRegenerated;
41+
}
42+
43+
44+
}

0 commit comments

Comments
 (0)