Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
session.php
1<?php
2
3namespace Bitrix\Main\Session;
4
10
11class Session implements SessionInterface, \ArrayAccess
12{
14
15 protected bool $started = false;
16 protected ?\SessionHandlerInterface $sessionHandler;
17 protected bool $lazyStartEnabled = false;
18 protected bool $debug = false;
20 protected bool $ignoringSessionStartErrors = false;
21 protected bool $useStrictMode = true;
22
26 public function __construct(\SessionHandlerInterface $sessionHandler = null)
27 {
28 $this->sessionHandler = $sessionHandler;
29 $this->debugger = new Debugger();
30
31 session_register_shutdown();
32 if ($this->sessionHandler)
33 {
34 session_set_save_handler($this->sessionHandler, false);
35 }
36 }
37
38 public function enableLazyStart(): self
39 {
40 $this->lazyStartEnabled = true;
41
42 return $this;
43 }
44
45 public function disableLazyStart(): self
46 {
47 $this->lazyStartEnabled = false;
48
49 return $this;
50 }
51
52 public function enableDebug(): self
53 {
54 $this->debug = true;
55
56 return $this;
57 }
58
59 public function disableDebug(): self
60 {
61 $this->debug = false;
62
63 return $this;
64 }
65
66 public function enableIgnoringSessionStartErrors(): self
67 {
68 $this->ignoringSessionStartErrors = true;
69
70 return $this;
71 }
72
73 public function disableIgnoringSessionStartErrors(): self
74 {
75 $this->ignoringSessionStartErrors = false;
76
77 return $this;
78 }
79
80 public function isActive(): bool
81 {
82 return session_status() === \PHP_SESSION_ACTIVE;
83 }
84
85 private function isHeadersSent(&$file, &$line): bool
86 {
87 return filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line);
88 }
89
90 public function isAccessible(): bool
91 {
92 return !$this->isHeadersSent($file, $line);
93 }
94
95 public function getId(): string
96 {
97 return session_id();
98 }
99
100 public function setId($id): void
101 {
102 if ($this->isActive())
103 {
104 throw new \RuntimeException('Could not change the ID of an active session');
105 }
106
107 session_id($id);
108 }
109
110 public function getName(): string
111 {
112 return session_name();
113 }
114
115 public function setName($name): void
116 {
117 if ($this->isActive())
118 {
119 throw new \RuntimeException('Could not change the name of an active session');
120 }
121
122 session_name($name);
123 }
124
125 public function getSessionHandler(): ?\SessionHandlerInterface
126 {
127 return $this->sessionHandler;
128 }
129
130 public function start(): bool
131 {
132 if ($this->isStarted())
133 {
134 return true;
135 }
136
137 if ($this->isActive())
138 {
139 throw new \RuntimeException('Could not start session by PHP because session is active.');
140 }
141
142 if ($this->isHeadersSent($file, $line))
143 {
144 throw new \RuntimeException(
145 "Could not start session because headers have already been sent. \"{$file}\":{$line}."
146 );
147 }
148
149 $this->debug('Session tries to start at');
150 $this->detectFirstUsage();
151
152 try
153 {
155 if (!session_start() && !$this->ignoringSessionStartErrors)
156 {
157 throw new \RuntimeException('Could not start session by PHP.');
158 }
159 }
160 catch (\Error $error)
161 {
162 if ($this->shouldLogError($error))
163 {
164 $this->writeToLogError($error);
165 }
166
167 if (!$this->ignoringSessionStartErrors)
168 {
169 throw $error->getPrevious() ?: $error;
170 }
171 }
172 $this->debug('Session started at');
173
174 $this->sessionData = &$_SESSION;
175 $this->started = true;
176
177 if ($this->has('destroyed'))
178 {
179 //todo 100? why?
180 if ($this->get('destroyed') < time() - 100)
181 {
182 $this->clear();
183 }
184 else
185 {
186 $newSessionId = $this->get('newSid');
187 $this->save();
188
189 $this->setId($newSessionId);
190
191 return $this->start();
192 }
193 }
194
195 return true;
196 }
197
198 protected function getSessionStartOptions(): array
199 {
200 $useStrictModeValue = $this->useStrictMode? 1 : 0;
201
202 if ($this->sessionHandler instanceof NullSessionHandler)
203 {
204 return [
205 'use_cookies' => 0,
206 'use_strict_mode' => $useStrictModeValue,
207 ];
208 }
209
210 $options = [
211 'cookie_httponly' => 1,
212 'use_strict_mode' => $useStrictModeValue,
213 ];
214
215 $domain = Cookie::getCookieDomain();
216 if ($domain)
217 {
218 $options['cookie_domain'] = $domain;
219 }
220
221 return $options;
222 }
223
224 protected function applySessionStartIniSettings(array $settings): void
225 {
226 foreach ($settings as $name => $value)
227 {
228 ini_set("session.{$name}", $value);
229 }
230 }
231
232 private function writeToLogError(\Error $error): void
233 {
234 $exceptionHandler = Application::getInstance()->getExceptionHandler();
235 $exceptionHandler->writeToLog($error);
236
237 if ($error->getPrevious())
238 {
239 $exceptionHandler->writeToLog($error->getPrevious());
240 }
241 }
242
243 private function shouldLogError(\Error $error): bool
244 {
245 if (!$error->getPrevious())
246 {
247 return true;
248 }
249
250 if (str_starts_with($error->getPrevious()->getMessage(), AbstractSessionHandler::LOCK_ERROR_MESSAGE))
251 {
252 return false;
253 }
254
255 return true;
256 }
257
258 public function regenerateId(): bool
259 {
260 if (!$this->isStarted())
261 {
262 return false;
263 }
264
265 $newSessionId = session_create_id();
266
267 $this->set('newSid', $newSessionId);
268 $this->set('destroyed', time());
269
270 $backup = $this->sessionData;
271 $this->saveWithoutReleaseLock();
272
273 $this->disableStrictMode();
274 $this->setId($newSessionId);
275// Idea to switch on strict mode after setId is good. But in that case
276// session_start will invoke validateId for one time more. So behavior in
277// 7.1, 7.2 & 7.4 is different and now it's way to avoid that.
278// if ($prevStrictMode !== false)
279// {
280// ini_set('session.use_strict_mode', $prevStrictMode);
281// }
282
283 $this->start();
284 $this->enableStrictMode();
285
286 $_SESSION = $backup;
287
288 $this->remove('newSid');
289 $this->remove('destroyed');
290
291 return true;
292 }
293
294 public function destroy(): void
295 {
296 if ($this->isActive())
297 {
298 session_destroy();
299 $this->started = false;
300 }
301 }
302
303 protected function saveWithoutReleaseLock(): void
304 {
305 if ($this->sessionHandler instanceof AbstractSessionHandler)
306 {
307 $this->sessionHandler->turnOffReleaseLockAfterCloseSession();
308 $this->save();
309 $this->sessionHandler->turnOnReleaseLockAfterCloseSession();
310 }
311 else
312 {
313 $this->save();
314 }
315 }
316
317 public function save(): void
318 {
319 $session = $_SESSION;
320
321 $previousHandler = set_error_handler(
322 function($type, $msg, $file, $line) use (&$previousHandler) {
323 if ($type === E_WARNING && str_starts_with($msg, 'session_write_close():'))
324 {
325 $handler = $this->sessionHandler;
326 $msg = sprintf(
327 "session_write_close(): Failed to write session data with \"%s\" handler",
328 $handler? \get_class($handler) : ''
329 );
330 }
331
332 return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
333 }
334 );
335
336 try
337 {
339 session_write_close();
340 }
341 finally
342 {
343 restore_error_handler();
344 if ($_SESSION)
345 {
346 $_SESSION = $session;
347 }
348 }
349
350 $this->started = false;
351 }
352
353 protected function processLazyStart(): bool
354 {
355 if (!$this->lazyStartEnabled)
356 {
357 return false;
358 }
359 if ($this->isStarted())
360 {
361 return false;
362 }
363
364 return $this->start();
365 }
366
367 public function clear(): void
368 {
369 $_SESSION = [];
370 $this->nullPointers = [];
371 }
372
373 public function isStarted(): bool
374 {
375 return $this->started;
376 }
377
381 public function getDebugger(): Debugger
382 {
383 return $this->debugger;
384 }
385
386 private function debug(string $text): void
387 {
388 if (!$this->debug)
389 {
390 return;
391 }
392
393 $this->getDebugger()->logToFile($text);
394 }
395
396 private function detectFirstUsage(): void
397 {
398 if (!$this->debug)
399 {
400 return;
401 }
402
403 $this->getDebugger()->detectFirstUsage();
404 }
405
406 private function enableStrictMode(): self
407 {
408 $this->useStrictMode = true;
409
410 return $this;
411 }
412
413 private function disableStrictMode(): self
414 {
415 ini_set('session.use_strict_mode', 0);
416 $this->useStrictMode = false;
417
418 return $this;
419 }
420}
__construct(\SessionHandlerInterface $sessionHandler=null)
Definition session.php:26
applySessionStartIniSettings(array $settings)
Definition session.php:224
SessionHandlerInterface $sessionHandler
Definition session.php:16