Skip to content

Commit 50ee1d7

Browse files
authored
Merge pull request #5 from flightphp/prefix-regenerate-destroy
Added prefix. Also fixed regenerate/destroy behavior
2 parents 5912667 + 4be3e59 commit 50ee1d7

File tree

2 files changed

+142
-140
lines changed

2 files changed

+142
-140
lines changed

src/Session.php

+62-52
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
class Session implements SessionHandlerInterface
1414
{
1515
private string $savePath;
16+
private string $prefix;
1617
private array $data = [];
1718
private bool $changed = false;
1819
private ?string $sessionId = null;
1920
private ?string $encryptionKey = null;
2021
private bool $autoCommit = true;
2122
private bool $testMode = false;
23+
private bool $inRegenerate = false;
2224

2325
/**
2426
* Constructor to initialize the session handler.
@@ -34,11 +36,12 @@ class Session implements SessionHandlerInterface
3436
public function __construct(array $config = [])
3537
{
3638
$this->savePath = $config['save_path'] ?? sys_get_temp_dir() . '/flight_sessions';
39+
$this->prefix = $config['prefix'] ?? 'sess_';
3740
$this->encryptionKey = $config['encryption_key'] ?? null;
3841
$this->autoCommit = $config['auto_commit'] ?? true;
3942
$startSession = $config['start_session'] ?? true;
4043
$this->testMode = $config['test_mode'] ?? false;
41-
44+
4245
// Set test session ID if provided
4346
if ($this->testMode === true && isset($config['test_session_id'])) {
4447
$this->sessionId = $config['test_session_id'];
@@ -52,7 +55,7 @@ public function __construct(array $config = [])
5255
// Initialize session handler
5356
$this->initializeSession($startSession);
5457
}
55-
58+
5659
/**
5760
* Initialize the session handler and optionally start the session.
5861
*
@@ -69,21 +72,19 @@ private function initializeSession(bool $startSession): void
6972
$this->read($this->sessionId); // Load session data for the test session ID
7073
return; // Skip actual session operations in test mode
7174
}
72-
75+
7376
// @codeCoverageIgnoreStart
7477
// Register the session handler only if no session is active yet
7578
if ($startSession === true && session_status() === PHP_SESSION_NONE) {
7679
// Make sure to register our handler before calling session_start
7780
session_set_save_handler($this, true);
78-
81+
7982
// Start the session with proper options
8083
session_start([
8184
'use_strict_mode' => true,
8285
'use_cookies' => 1,
8386
'use_only_cookies' => 1,
84-
'cookie_httponly' => 1,
85-
'sid_length' => 48,
86-
'sid_bits_per_character' => 6
87+
'cookie_httponly' => 1
8788
]);
8889
$this->sessionId = session_id();
8990
} elseif (session_status() === PHP_SESSION_ACTIVE) {
@@ -98,7 +99,7 @@ private function initializeSession(bool $startSession): void
9899
// @codeCoverageIgnoreEnd
99100
}
100101

101-
102+
102103
/**
103104
* Open a session.
104105
*
@@ -131,6 +132,7 @@ public function close(): bool
131132
* @param string $id The session ID.
132133
* @return string The session data.
133134
*/
135+
#[\ReturnTypeWillChange]
134136
public function read($id): string
135137
{
136138
$this->sessionId = $id;
@@ -155,39 +157,27 @@ public function read($id): string
155157

156158
// Handle plain data (no encryption)
157159
if ($prefix === 'P' && $this->encryptionKey === null) {
158-
try {
159-
$unserialized = unserialize($dataStr);
160-
if ($unserialized !== false) {
161-
$this->data = $unserialized;
162-
return ''; // Return empty string to let PHP handle serialization
163-
}
164-
} catch (\Exception $e) {
165-
// Silently handle unserialization errors
160+
$unserialized = unserialize($dataStr);
161+
if ($unserialized !== false) {
162+
$this->data = $unserialized;
163+
return ''; // Return empty string to let PHP handle serialization
166164
}
167-
168-
$this->data = [];
169-
return '';
170165
}
171166

172167
// Handle encrypted data
173168
if ($prefix === 'E' && $this->encryptionKey !== null) {
174-
try {
175169
$iv = substr($dataStr, 0, 16);
176170
$encrypted = substr($dataStr, 16);
177171
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $this->encryptionKey, 0, $iv);
178172

179-
if ($decrypted !== false) {
180-
$unserialized = unserialize($decrypted);
181-
if ($unserialized !== false) {
182-
$this->data = $unserialized;
183-
return '';
184-
}
173+
if ($decrypted !== false) {
174+
$unserialized = unserialize($decrypted);
175+
if ($unserialized !== false) {
176+
$this->data = $unserialized;
177+
return '';
185178
}
186-
} catch (\Exception $e) {
187-
// Silently handle decryption or unserialization errors
188179
}
189180
}
190-
191181
// Fail fast: mismatch between prefix and encryption state or corruption
192182
$this->data = [];
193183
return '';
@@ -204,11 +194,11 @@ protected function encryptData(string $data)
204194
{
205195
$iv = openssl_random_pseudo_bytes(16);
206196
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $this->encryptionKey, 0, $iv);
207-
197+
208198
if ($encrypted === false) {
209199
return false; // @codeCoverageIgnore
210200
}
211-
201+
212202
return 'E' . $iv . $encrypted;
213203
}
214204

@@ -220,7 +210,7 @@ public function write($id, $data): bool
220210
// When PHP calls this method, it passes serialized data
221211
// We ignore this parameter because we maintain our data internally
222212
// and handle serialization ourselves
223-
213+
224214
// Fail fast: no changes to write
225215
if ($this->changed === false && empty($this->data) === false) {
226216
return true;
@@ -232,7 +222,7 @@ public function write($id, $data): bool
232222
// Handle encryption if key is provided
233223
if ($this->encryptionKey !== null) {
234224
$content = $this->encryptData($serialized);
235-
225+
236226
// Fail fast: encryption failed
237227
if ($content === false) {
238228
return false;
@@ -253,12 +243,27 @@ public function write($id, $data): bool
253243
*/
254244
public function destroy($id): bool
255245
{
246+
// If we're destroying the current session, clear the data
247+
if ($id === $this->sessionId) {
248+
$this->data = [];
249+
$this->changed = true;
250+
$this->autoCommit = false; // Disable auto-commit to prevent writing empty data
251+
$this->commit();
252+
if ($this->testMode === false && $this->inRegenerate === false && session_status() === PHP_SESSION_ACTIVE) {
253+
// Ensure session is closed
254+
session_write_close(); // @codeCoverageIgnore
255+
}
256+
$this->sessionId = null; // Clear session ID
257+
}
258+
256259
$file = $this->getSessionFile($id);
257-
if (file_exists($file)) {
258-
unlink($file);
260+
if (file_exists($file) === true) {
261+
$result = unlink($file);
262+
if ($result === false) {
263+
return false; // @codeCoverageIgnore
264+
}
259265
}
260-
$this->data = [];
261-
$this->changed = true;
266+
262267
return true;
263268
}
264269

@@ -276,7 +281,7 @@ public function gc($maxLifetime)
276281
{
277282
$count = 0;
278283
$time = time();
279-
$pattern = $this->savePath . '/sess_*';
284+
$pattern = $this->savePath . '/' . $this->prefix . '*';
280285

281286
// Get session files; return 0 if glob fails or no files exist
282287
$files = glob($pattern);
@@ -382,29 +387,34 @@ public function id(): ?string
382387
/**
383388
* Regenerates the session ID.
384389
*
385-
* @param bool $deleteOld Whether to delete the old session data or not.
390+
* @param bool $deleteOldFile Whether to delete the old session data or not.
386391
* @return self Returns the current instance for method chaining.
387392
*/
388-
public function regenerate(bool $deleteOld = false): self
393+
public function regenerate(bool $deleteOldFile = false): self
389394
{
390395
if ($this->sessionId) {
396+
$oldId = $this->sessionId;
397+
$oldData = $this->data;
398+
$this->inRegenerate = true;
399+
391400
if ($this->testMode) {
392-
// In test mode, simply generate a new ID without affecting PHP sessions
393-
$oldId = $this->sessionId;
401+
// In test mode, generate a new ID without affecting PHP sessions
394402
$this->sessionId = bin2hex(random_bytes(16));
395-
if ($deleteOld) {
396-
$this->destroy($oldId);
397-
}
398403
} else {
399404
// @codeCoverageIgnoreStart
400-
session_regenerate_id($deleteOld);
401-
$newId = session_id();
402-
if ($deleteOld) {
403-
$this->destroy($this->sessionId);
404-
}
405-
$this->sessionId = $newId;
405+
session_regenerate_id($deleteOldFile);
406+
$this->sessionId = session_id();
406407
// @codeCoverageIgnoreEnd
407408
}
409+
$this->inRegenerate = false;
410+
411+
// Save the current data with the new session ID first
412+
if (empty($oldData) === false) {
413+
$this->changed = true;
414+
$this->data = $oldData;
415+
$this->commit();
416+
}
417+
408418
$this->changed = true;
409419
}
410420
return $this;
@@ -418,6 +428,6 @@ public function regenerate(bool $deleteOld = false): self
418428
*/
419429
private function getSessionFile(string $id): string
420430
{
421-
return $this->savePath . '/sess_' . $id;
431+
return $this->savePath . '/' . $this->prefix . $id;
422432
}
423433
}

0 commit comments

Comments
 (0)