• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 12518821104

27 Dec 2024 05:21PM UTC coverage: 84.426% (+0.02%) from 84.404%
12518821104

Pull #9339

github

web-flow
Merge 5caee6ae0 into 6cbbf601b
Pull Request #9339: refactor: enable instanceof and strictBooleans rector set

55 of 60 new or added lines in 34 files covered. (91.67%)

19 existing lines in 3 files now uncovered.

20437 of 24207 relevant lines covered (84.43%)

189.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

15.93
/system/Session/Handlers/FileHandler.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Session\Handlers;
15

16
use CodeIgniter\I18n\Time;
17
use CodeIgniter\Session\Exceptions\SessionException;
18
use Config\Session as SessionConfig;
19
use ReturnTypeWillChange;
20

21
/**
22
 * Session handler using file system for storage
23
 */
24
class FileHandler extends BaseHandler
25
{
26
    /**
27
     * Where to save the session files to.
28
     *
29
     * @var string
30
     */
31
    protected $savePath;
32

33
    /**
34
     * The file handle
35
     *
36
     * @var resource|null
37
     */
38
    protected $fileHandle;
39

40
    /**
41
     * File Name
42
     *
43
     * @var string
44
     */
45
    protected $filePath;
46

47
    /**
48
     * Whether this is a new file.
49
     *
50
     * @var bool
51
     */
52
    protected $fileNew;
53

54
    /**
55
     * Whether IP addresses should be matched.
56
     *
57
     * @var bool
58
     */
59
    protected $matchIP = false;
60

61
    /**
62
     * Regex of session ID
63
     *
64
     * @var string
65
     */
66
    protected $sessionIDRegex = '';
67

68
    public function __construct(SessionConfig $config, string $ipAddress)
69
    {
70
        parent::__construct($config, $ipAddress);
87✔
71

72
        if (! empty($this->savePath)) {
87✔
73
            $this->savePath = rtrim($this->savePath, '/\\');
39✔
74
            ini_set('session.save_path', $this->savePath);
39✔
75
        } else {
76
            $sessionPath = rtrim(ini_get('session.save_path'), '/\\');
48✔
77

78
            if ($sessionPath === '') {
48✔
79
                $sessionPath = WRITEPATH . 'session';
×
80
            }
81

82
            $this->savePath = $sessionPath;
48✔
83
        }
84

85
        $this->configureSessionIDRegex();
87✔
86
    }
87

88
    /**
89
     * Re-initialize existing session, or creates a new one.
90
     *
91
     * @param string $path The path where to store/retrieve the session
92
     * @param string $name The session name
93
     *
94
     * @throws SessionException
95
     */
96
    public function open($path, $name): bool
97
    {
98
        if (! is_dir($path) && ! mkdir($path, 0700, true)) {
×
99
            throw SessionException::forInvalidSavePath($this->savePath);
×
100
        }
101

102
        if (! is_writable($path)) {
×
103
            throw SessionException::forWriteProtectedSavePath($this->savePath);
×
104
        }
105

106
        $this->savePath = $path;
×
107

108
        // we'll use the session name as prefix to avoid collisions
109
        $this->filePath = $this->savePath . '/' . $name . ($this->matchIP ? md5($this->ipAddress) : '');
×
110

111
        return true;
×
112
    }
113

114
    /**
115
     * Reads the session data from the session storage, and returns the results.
116
     *
117
     * @param string $id The session ID
118
     *
119
     * @return false|string Returns an encoded string of the read data.
120
     *                      If nothing was read, it must return false.
121
     */
122
    #[ReturnTypeWillChange]
123
    public function read($id)
124
    {
125
        // This might seem weird, but PHP 5.6 introduced session_reset(),
126
        // which re-reads session data
127
        if ($this->fileHandle === null) {
×
128
            $this->fileNew = ! is_file($this->filePath . $id);
×
129

130
            if (($this->fileHandle = fopen($this->filePath . $id, 'c+b')) === false) {
×
131
                $this->logger->error("Session: Unable to open file '" . $this->filePath . $id . "'.");
×
132

133
                return false;
×
134
            }
135

136
            if (flock($this->fileHandle, LOCK_EX) === false) {
×
137
                $this->logger->error("Session: Unable to obtain lock for file '" . $this->filePath . $id . "'.");
×
138
                fclose($this->fileHandle);
×
139
                $this->fileHandle = null;
×
140

141
                return false;
×
142
            }
143

144
            if (! isset($this->sessionID)) {
×
145
                $this->sessionID = $id;
×
146
            }
147

148
            if ($this->fileNew) {
×
149
                chmod($this->filePath . $id, 0600);
×
150
                $this->fingerprint = md5('');
×
151

152
                return '';
×
153
            }
154
        } else {
155
            rewind($this->fileHandle);
×
156
        }
157

158
        $data   = '';
×
159
        $buffer = 0;
×
160
        clearstatcache(); // Address https://github.com/codeigniter4/CodeIgniter4/issues/2056
×
161

162
        for ($read = 0, $length = filesize($this->filePath . $id); $read < $length; $read += strlen($buffer)) {
×
163
            if (($buffer = fread($this->fileHandle, $length - $read)) === false) {
×
164
                break;
×
165
            }
166

167
            $data .= $buffer;
×
168
        }
169

170
        $this->fingerprint = md5($data);
×
171

172
        return $data;
×
173
    }
174

175
    /**
176
     * Writes the session data to the session storage.
177
     *
178
     * @param string $id   The session ID
179
     * @param string $data The encoded session data
180
     */
181
    public function write($id, $data): bool
182
    {
183
        // If the two IDs don't match, we have a session_regenerate_id() call
184
        if ($id !== $this->sessionID) {
×
185
            $this->sessionID = $id;
×
186
        }
187

188
        if (! is_resource($this->fileHandle)) {
×
189
            return false;
×
190
        }
191

192
        if ($this->fingerprint === md5($data)) {
×
193
            return ($this->fileNew) ? true : touch($this->filePath . $id);
×
194
        }
195

196
        if (! $this->fileNew) {
×
197
            ftruncate($this->fileHandle, 0);
×
198
            rewind($this->fileHandle);
×
199
        }
200

201
        if (($length = strlen($data)) > 0) {
×
202
            $result = null;
×
203

204
            for ($written = 0; $written < $length; $written += $result) {
×
205
                if (($result = fwrite($this->fileHandle, substr($data, $written))) === false) {
×
206
                    break;
×
207
                }
208
            }
209

210
            if (! is_int($result)) {
×
211
                $this->fingerprint = md5(substr($data, 0, $written));
×
212
                $this->logger->error('Session: Unable to write data.');
×
213

214
                return false;
×
215
            }
216
        }
217

218
        $this->fingerprint = md5($data);
×
219

220
        return true;
×
221
    }
222

223
    /**
224
     * Closes the current session.
225
     */
226
    public function close(): bool
227
    {
228
        if (is_resource($this->fileHandle)) {
×
229
            flock($this->fileHandle, LOCK_UN);
×
230
            fclose($this->fileHandle);
×
231

232
            $this->fileHandle = null;
×
233
            $this->fileNew    = false;
×
234
        }
235

236
        return true;
×
237
    }
238

239
    /**
240
     * Destroys a session
241
     *
242
     * @param string $id The session ID being destroyed
243
     */
244
    public function destroy($id): bool
245
    {
246
        if ($this->close()) {
×
247
            return is_file($this->filePath . $id)
×
248
                ? (unlink($this->filePath . $id) && $this->destroyCookie())
×
249
                : true;
×
250
        }
251

252
        if ($this->filePath !== null) {
×
253
            clearstatcache();
×
254

255
            return is_file($this->filePath . $id)
×
256
                ? (unlink($this->filePath . $id) && $this->destroyCookie())
×
257
                : true;
×
258
        }
259

260
        return false;
×
261
    }
262

263
    /**
264
     * Cleans up expired sessions.
265
     *
266
     * @param int $max_lifetime Sessions that have not updated
267
     *                          for the last max_lifetime seconds will be removed.
268
     *
269
     * @return false|int Returns the number of deleted sessions on success, or false on failure.
270
     */
271
    #[ReturnTypeWillChange]
272
    public function gc($max_lifetime)
273
    {
274
        if (! is_dir($this->savePath) || ($directory = opendir($this->savePath)) === false) {
×
275
            $this->logger->debug("Session: Garbage collector couldn't list files under directory '" . $this->savePath . "'.");
×
276

277
            return false;
×
278
        }
279

280
        $ts = Time::now()->getTimestamp() - $max_lifetime;
×
281

282
        $pattern = $this->matchIP === true ? '[0-9a-f]{32}' : '';
×
283

284
        $pattern = sprintf(
×
285
            '#\A%s' . $pattern . $this->sessionIDRegex . '\z#',
×
286
            preg_quote($this->cookieName, '#')
×
287
        );
×
288

289
        $collected = 0;
×
290

291
        while (($file = readdir($directory)) !== false) {
×
292
            // If the filename doesn't match this pattern, it's either not a session file or is not ours
NEW
293
            if (preg_match($pattern, $file) !== 1
×
294
                || ! is_file($this->savePath . DIRECTORY_SEPARATOR . $file)
×
295
                || ($mtime = filemtime($this->savePath . DIRECTORY_SEPARATOR . $file)) === false
×
296
                || $mtime > $ts
×
297
            ) {
298
                continue;
×
299
            }
300

301
            unlink($this->savePath . DIRECTORY_SEPARATOR . $file);
×
302
            $collected++;
×
303
        }
304

305
        closedir($directory);
×
306

307
        return $collected;
×
308
    }
309

310
    /**
311
     * Configure Session ID regular expression
312
     */
313
    protected function configureSessionIDRegex()
314
    {
315
        $bitsPerCharacter = (int) ini_get('session.sid_bits_per_character');
87✔
316
        $SIDLength        = (int) ini_get('session.sid_length');
87✔
317

318
        if (($bits = $SIDLength * $bitsPerCharacter) < 160) {
87✔
319
            // Add as many more characters as necessary to reach at least 160 bits
320
            $SIDLength += (int) ceil((160 % $bits) / $bitsPerCharacter);
87✔
321
            ini_set('session.sid_length', (string) $SIDLength);
87✔
322
        }
323

324
        switch ($bitsPerCharacter) {
325
            case 4:
87✔
326
                $this->sessionIDRegex = '[0-9a-f]';
×
327
                break;
×
328

329
            case 5:
87✔
330
                $this->sessionIDRegex = '[0-9a-v]';
87✔
331
                break;
87✔
332

333
            case 6:
×
334
                $this->sessionIDRegex = '[0-9a-zA-Z,-]';
×
335
                break;
×
336
        }
337

338
        $this->sessionIDRegex .= '{' . $SIDLength . '}';
87✔
339
    }
340
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc