| 3 |
liveuser |
1 |
<?php
|
|
|
2 |
namespace Ratchet\Session;
|
|
|
3 |
use Ratchet\ConnectionInterface;
|
|
|
4 |
use Ratchet\Http\HttpServerInterface;
|
|
|
5 |
use Psr\Http\Message\RequestInterface;
|
|
|
6 |
use Ratchet\Session\Storage\VirtualSessionStorage;
|
|
|
7 |
use Ratchet\Session\Serialize\HandlerInterface;
|
|
|
8 |
use Symfony\Component\HttpFoundation\Session\Session;
|
|
|
9 |
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
|
|
|
10 |
|
|
|
11 |
/**
|
|
|
12 |
* This component will allow access to session data from your website for each user connected
|
|
|
13 |
* Symfony HttpFoundation is required for this component to work
|
|
|
14 |
* Your website must also use Symfony HttpFoundation Sessions to read your sites session data
|
|
|
15 |
* If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer)
|
|
|
16 |
*/
|
|
|
17 |
class SessionProvider implements HttpServerInterface {
|
|
|
18 |
/**
|
|
|
19 |
* @var \Ratchet\MessageComponentInterface
|
|
|
20 |
*/
|
|
|
21 |
protected $_app;
|
|
|
22 |
|
|
|
23 |
/**
|
|
|
24 |
* Selected handler storage assigned by the developer
|
|
|
25 |
* @var \SessionHandlerInterface
|
|
|
26 |
*/
|
|
|
27 |
protected $_handler;
|
|
|
28 |
|
|
|
29 |
/**
|
|
|
30 |
* Null storage handler if no previous session was found
|
|
|
31 |
* @var \SessionHandlerInterface
|
|
|
32 |
*/
|
|
|
33 |
protected $_null;
|
|
|
34 |
|
|
|
35 |
/**
|
|
|
36 |
* @var \Ratchet\Session\Serialize\HandlerInterface
|
|
|
37 |
*/
|
|
|
38 |
protected $_serializer;
|
|
|
39 |
|
|
|
40 |
/**
|
|
|
41 |
* @param \Ratchet\Http\HttpServerInterface $app
|
|
|
42 |
* @param \SessionHandlerInterface $handler
|
|
|
43 |
* @param array $options
|
|
|
44 |
* @param \Ratchet\Session\Serialize\HandlerInterface $serializer
|
|
|
45 |
* @throws \RuntimeException
|
|
|
46 |
*/
|
|
|
47 |
public function __construct(HttpServerInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) {
|
|
|
48 |
$this->_app = $app;
|
|
|
49 |
$this->_handler = $handler;
|
|
|
50 |
$this->_null = new NullSessionHandler;
|
|
|
51 |
|
|
|
52 |
ini_set('session.auto_start', 0);
|
|
|
53 |
ini_set('session.cache_limiter', '');
|
|
|
54 |
ini_set('session.use_cookies', 0);
|
|
|
55 |
|
|
|
56 |
$this->setOptions($options);
|
|
|
57 |
|
|
|
58 |
if (null === $serializer) {
|
|
|
59 |
$serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh?
|
|
|
60 |
if (!class_exists($serialClass)) {
|
|
|
61 |
throw new \RuntimeException('Unable to parse session serialize handler');
|
|
|
62 |
}
|
|
|
63 |
|
|
|
64 |
$serializer = new $serialClass;
|
|
|
65 |
}
|
|
|
66 |
|
|
|
67 |
$this->_serializer = $serializer;
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
/**
|
|
|
71 |
* {@inheritdoc}
|
|
|
72 |
*/
|
|
|
73 |
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
|
|
|
74 |
$sessionName = ini_get('session.name');
|
|
|
75 |
|
|
|
76 |
$id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) {
|
|
|
77 |
if ($accumulator) {
|
|
|
78 |
return $accumulator;
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
$crumbs = $this->parseCookie($cookie);
|
|
|
82 |
|
|
|
83 |
return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false;
|
|
|
84 |
}, false);
|
|
|
85 |
|
|
|
86 |
if (null === $request || false === $id) {
|
|
|
87 |
$saveHandler = $this->_null;
|
|
|
88 |
$id = '';
|
|
|
89 |
} else {
|
|
|
90 |
$saveHandler = $this->_handler;
|
|
|
91 |
}
|
|
|
92 |
|
|
|
93 |
$conn->Session = new Session(new VirtualSessionStorage($saveHandler, $id, $this->_serializer));
|
|
|
94 |
|
|
|
95 |
if (ini_get('session.auto_start')) {
|
|
|
96 |
$conn->Session->start();
|
|
|
97 |
}
|
|
|
98 |
|
|
|
99 |
return $this->_app->onOpen($conn, $request);
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
/**
|
|
|
103 |
* {@inheritdoc}
|
|
|
104 |
*/
|
|
|
105 |
function onMessage(ConnectionInterface $from, $msg) {
|
|
|
106 |
return $this->_app->onMessage($from, $msg);
|
|
|
107 |
}
|
|
|
108 |
|
|
|
109 |
/**
|
|
|
110 |
* {@inheritdoc}
|
|
|
111 |
*/
|
|
|
112 |
function onClose(ConnectionInterface $conn) {
|
|
|
113 |
// "close" session for Connection
|
|
|
114 |
|
|
|
115 |
return $this->_app->onClose($conn);
|
|
|
116 |
}
|
|
|
117 |
|
|
|
118 |
/**
|
|
|
119 |
* {@inheritdoc}
|
|
|
120 |
*/
|
|
|
121 |
function onError(ConnectionInterface $conn, \Exception $e) {
|
|
|
122 |
return $this->_app->onError($conn, $e);
|
|
|
123 |
}
|
|
|
124 |
|
|
|
125 |
/**
|
|
|
126 |
* Set all the php session. ini options
|
|
|
127 |
* © Symfony
|
|
|
128 |
* @param array $options
|
|
|
129 |
* @return array
|
|
|
130 |
*/
|
|
|
131 |
protected function setOptions(array $options) {
|
|
|
132 |
$all = array(
|
|
|
133 |
'auto_start', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
|
|
|
134 |
'cookie_lifetime', 'cookie_path', 'cookie_secure',
|
|
|
135 |
'entropy_file', 'entropy_length', 'gc_divisor',
|
|
|
136 |
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
|
|
|
137 |
'hash_function', 'name', 'referer_check',
|
|
|
138 |
'serialize_handler', 'use_cookies',
|
|
|
139 |
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
|
|
|
140 |
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
|
|
|
141 |
'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags'
|
|
|
142 |
);
|
|
|
143 |
|
|
|
144 |
foreach ($all as $key) {
|
|
|
145 |
if (!array_key_exists($key, $options)) {
|
|
|
146 |
$options[$key] = ini_get("session.{$key}");
|
|
|
147 |
} else {
|
|
|
148 |
ini_set("session.{$key}", $options[$key]);
|
|
|
149 |
}
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
return $options;
|
|
|
153 |
}
|
|
|
154 |
|
|
|
155 |
/**
|
|
|
156 |
* @param string $langDef Input to convert
|
|
|
157 |
* @return string
|
|
|
158 |
*/
|
|
|
159 |
protected function toClassCase($langDef) {
|
|
|
160 |
return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef)));
|
|
|
161 |
}
|
|
|
162 |
|
|
|
163 |
/**
|
|
|
164 |
* Taken from Guzzle3
|
|
|
165 |
*/
|
|
|
166 |
private static $cookieParts = array(
|
|
|
167 |
'domain' => 'Domain',
|
|
|
168 |
'path' => 'Path',
|
|
|
169 |
'max_age' => 'Max-Age',
|
|
|
170 |
'expires' => 'Expires',
|
|
|
171 |
'version' => 'Version',
|
|
|
172 |
'secure' => 'Secure',
|
|
|
173 |
'port' => 'Port',
|
|
|
174 |
'discard' => 'Discard',
|
|
|
175 |
'comment' => 'Comment',
|
|
|
176 |
'comment_url' => 'Comment-Url',
|
|
|
177 |
'http_only' => 'HttpOnly'
|
|
|
178 |
);
|
|
|
179 |
|
|
|
180 |
/**
|
|
|
181 |
* Taken from Guzzle3
|
|
|
182 |
*/
|
|
|
183 |
private function parseCookie($cookie, $host = null, $path = null, $decode = false) {
|
|
|
184 |
// Explode the cookie string using a series of semicolons
|
|
|
185 |
$pieces = array_filter(array_map('trim', explode(';', $cookie)));
|
|
|
186 |
|
|
|
187 |
// The name of the cookie (first kvp) must include an equal sign.
|
|
|
188 |
if (empty($pieces) || !strpos($pieces[0], '=')) {
|
|
|
189 |
return false;
|
|
|
190 |
}
|
|
|
191 |
|
|
|
192 |
// Create the default return array
|
|
|
193 |
$data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array(
|
|
|
194 |
'cookies' => array(),
|
|
|
195 |
'data' => array(),
|
|
|
196 |
'path' => $path ?: '/',
|
|
|
197 |
'http_only' => false,
|
|
|
198 |
'discard' => false,
|
|
|
199 |
'domain' => $host
|
|
|
200 |
));
|
|
|
201 |
$foundNonCookies = 0;
|
|
|
202 |
|
|
|
203 |
// Add the cookie pieces into the parsed data array
|
|
|
204 |
foreach ($pieces as $part) {
|
|
|
205 |
|
|
|
206 |
$cookieParts = explode('=', $part, 2);
|
|
|
207 |
$key = trim($cookieParts[0]);
|
|
|
208 |
|
|
|
209 |
if (count($cookieParts) == 1) {
|
|
|
210 |
// Can be a single value (e.g. secure, httpOnly)
|
|
|
211 |
$value = true;
|
|
|
212 |
} else {
|
|
|
213 |
// Be sure to strip wrapping quotes
|
|
|
214 |
$value = trim($cookieParts[1], " \n\r\t\0\x0B\"");
|
|
|
215 |
if ($decode) {
|
|
|
216 |
$value = urldecode($value);
|
|
|
217 |
}
|
|
|
218 |
}
|
|
|
219 |
|
|
|
220 |
// Only check for non-cookies when cookies have been found
|
|
|
221 |
if (!empty($data['cookies'])) {
|
|
|
222 |
foreach (self::$cookieParts as $mapValue => $search) {
|
|
|
223 |
if (!strcasecmp($search, $key)) {
|
|
|
224 |
$data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value;
|
|
|
225 |
$foundNonCookies++;
|
|
|
226 |
continue 2;
|
|
|
227 |
}
|
|
|
228 |
}
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
// If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a
|
|
|
232 |
// cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data.
|
|
|
233 |
$data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value;
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
// Calculate the expires date
|
|
|
237 |
if (!$data['expires'] && $data['max_age']) {
|
|
|
238 |
$data['expires'] = time() + (int) $data['max_age'];
|
|
|
239 |
}
|
|
|
240 |
|
|
|
241 |
return $data;
|
|
|
242 |
}
|
|
|
243 |
}
|