XoopsSessionHandler   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 108
dl 0
loc 278
rs 9.92
c 0
b 0
f 0
wmc 31

10 Methods

Rating   Name   Duplication   Size   Complexity  
A gc_force() 0 6 3
A close() 0 5 1
A open() 0 3 1
A gc() 0 10 2
B update_cookie() 0 25 8
A write() 0 27 2
A __construct() 0 22 4
A destroy() 0 12 2
A read() 0 27 5
A regenerate_id() 0 14 3
1
<?php
2
/**
3
 * XOOPS session handler
4
 *
5
 * You may not change or alter any portion of this comment or credits
6
 * of supporting developers from this source code or any supporting source code
7
 * which is considered copyrighted (c) material of the original comment or credit authors.
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
 *
12
 * @copyright       (c) 2000-2016 XOOPS Project (www.xoops.org)
13
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14
 * @package             kernel
15
 * @since               2.0.0
16
 * @author              Kazumi Ono (AKA onokazu) http://www.myweb.ne.jp/, http://jp.xoops.org/
17
 * @author              Taiwen Jiang <[email protected]>
18
 */
19
20
defined('XOOPS_ROOT_PATH') || exit('Restricted access');
21
22
/**
23
 * Handler for a session
24
 * @package             kernel
25
 *
26
 * @author              Kazumi Ono    <[email protected]>
27
 * @author              Taiwen Jiang <[email protected]>
28
 * @copyright       (c) 2000-2016 XOOPS Project (www.xoops.org)
29
 */
30
class XoopsSessionHandler
31
{
32
    /**
33
     * Database connection
34
     *
35
     * @var object
36
     * @access    private
37
     */
38
    public $db;
39
40
    /**
41
     * Security checking level
42
     *
43
     * Possible value:
44
     *    0 - no check;
45
     *    1 - check browser characteristics (HTTP_USER_AGENT/HTTP_ACCEPT_LANGUAGE), to be implemented in the future now;
46
     *    2 - check browser and IP A.B;
47
     *    3 - check browser and IP A.B.C, recommended;
48
     *    4 - check browser and IP A.B.C.D;
49
     *
50
     * @var int
51
     * @access    public
52
     */
53
    public $securityLevel = 3;
54
55
    protected $bitMasks = array(
56
        2 => array('v4' => 16, 'v6' => 64),
57
        3 => array('v4' => 24, 'v6' => 56),
58
        4 => array('v4' => 32, 'v6' => 128),
59
    );
60
61
    /**
62
     * Enable regenerate_id
63
     *
64
     * @var bool
65
     * @access    public
66
     */
67
    public $enableRegenerateId = true;
68
69
    /**
70
     * Constructor
71
     *
72
     * @param XoopsDatabase $db reference to the {@link XoopsDatabase} object
73
     *
74
     */
75
    public function __construct(XoopsDatabase $db)
76
    {
77
        global $xoopsConfig;
78
79
        $this->db = $db;
80
        // after php 7.3 we just let php handle the session cookie
81
        $lifetime = ($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '')
82
            ? $xoopsConfig['session_expire'] * 60
83
            : ini_get('session.cookie_lifetime');
84
        $secure = (XOOPS_PROT === 'https://');
85
        if (PHP_VERSION_ID >= 70300) {
86
            $options = array(
87
                'lifetime' => $lifetime,
88
                'path'     => '/',
89
                'domain'   => XOOPS_COOKIE_DOMAIN,
90
                'secure'   => $secure,
91
                'httponly' => true,
92
                'samesite' => 'strict',
93
            );
94
            session_set_cookie_params($options);
95
        } else {
96
            session_set_cookie_params($lifetime, '/', XOOPS_COOKIE_DOMAIN, $secure, true);
0 ignored issues
show
Bug introduced by
It seems like $lifetime can also be of type string; however, parameter $lifetime_or_options of session_set_cookie_params() does only seem to accept array|integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
            session_set_cookie_params(/** @scrutinizer ignore-type */ $lifetime, '/', XOOPS_COOKIE_DOMAIN, $secure, true);
Loading history...
97
        }
98
    }
99
100
    /**
101
     * Open a session
102
     *
103
     * @param string $savePath
104
     * @param string $sessionName
105
     *
106
     * @return bool
107
     */
108
    public function open($savePath, $sessionName)
0 ignored issues
show
Unused Code introduced by
The parameter $sessionName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

108
    public function open($savePath, /** @scrutinizer ignore-unused */ $sessionName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $savePath is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

108
    public function open(/** @scrutinizer ignore-unused */ $savePath, $sessionName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
109
    {
110
        return true;
111
    }
112
113
    /**
114
     * Close a session
115
     *
116
     * @return bool
117
     */
118
    public function close()
119
    {
120
        $this->gc_force();
121
122
        return true;
123
    }
124
125
    /**
126
     * Read a session from the database
127
     *
128
     * @param string $sessionId ID of the session
129
     *
130
     * @return string Session data
131
     */
132
    public function read($sessionId)
133
    {
134
        $ip = \Xmf\IPAddress::fromRequest();
135
        $sql = sprintf(
136
            'SELECT sess_data, sess_ip FROM %s WHERE sess_id = %s',
137
            $this->db->prefix('session'),
138
            $this->db->quoteString($sessionId)
139
        );
140
141
        $result = $this->db->query($sql);
142
        if ($this->db->isResultSet($result)) {
143
            if (list($sess_data, $sess_ip) = $this->db->fetchRow($result)) {
144
                if ($this->securityLevel > 1) {
145
                    if (false === $ip->sameSubnet(
146
                        $sess_ip,
147
                        $this->bitMasks[$this->securityLevel]['v4'],
148
                        $this->bitMasks[$this->securityLevel]['v6']
149
                    )) {
150
                        $sess_data = '';
151
                    }
152
                }
153
154
                return $sess_data;
155
            }
156
        }
157
158
        return '';
159
    }
160
161
    /**
162
     * Write a session to the database
163
     *
164
     * @param string $sessionId
165
     * @param string $data
166
     *
167
     * @return bool
168
     **/
169
    public function write($sessionId, $data)
170
    {
171
        $myReturn = true;
172
        $remoteAddress = \Xmf\IPAddress::fromRequest()->asReadable();
173
        $sessionId = $this->db->quoteString($sessionId);
174
        $sql = sprintf(
175
            'UPDATE %s SET sess_updated = %u, sess_data = %s WHERE sess_id = %s',
176
            $this->db->prefix('session'),
177
            time(),
178
            $this->db->quoteString($data),
179
            $sessionId
180
        );
181
        $this->db->queryF($sql);
182
        if (!$this->db->getAffectedRows()) {
183
            $sql = sprintf(
184
                'INSERT INTO %s (sess_id, sess_updated, sess_ip, sess_data) VALUES (%s, %u, %s, %s)',
185
                $this->db->prefix('session'),
186
                $sessionId,
187
                time(),
188
                $this->db->quote($remoteAddress),
189
                $this->db->quote($data)
190
            );
191
192
            $myReturn = $this->db->queryF($sql);
193
        }
194
        $this->update_cookie();
195
        return $myReturn;
196
    }
197
198
    /**
199
     * Destroy a session
200
     *
201
     * @param string $sessionId
202
     *
203
     * @return bool
204
     **/
205
    public function destroy($sessionId)
206
    {
207
        $sql = sprintf(
208
            'DELETE FROM %s WHERE sess_id = %s',
209
            $this->db->prefix('session'),
210
            $this->db->quoteString($sessionId)
211
        );
212
        if (!$result = $this->db->queryF($sql)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
213
            return false;
214
        }
215
216
        return true;
217
    }
218
219
    /**
220
     * Garbage Collector
221
     *
222
     * @param  int $expire Time in seconds until a session expires
223
     * @return bool
224
     **/
225
    public function gc($expire)
226
    {
227
        if (empty($expire)) {
228
            return true;
229
        }
230
231
        $mintime = time() - (int)$expire;
232
        $sql     = sprintf('DELETE FROM %s WHERE sess_updated < %u', $this->db->prefix('session'), $mintime);
233
234
        return $this->db->queryF($sql);
235
    }
236
237
    /**
238
     * Force gc for situations where gc is registered but not executed
239
     **/
240
    public function gc_force()
241
    {
242
        if (mt_rand(1, 100) < 11) {
243
            $expire = @ini_get('session.gc_maxlifetime');
244
            $expire = ($expire > 0) ? $expire : 900;
245
            $this->gc($expire);
0 ignored issues
show
Bug introduced by
It seems like $expire can also be of type false and string; however, parameter $expire of XoopsSessionHandler::gc() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

245
            $this->gc(/** @scrutinizer ignore-type */ $expire);
Loading history...
246
        }
247
    }
248
249
    /**
250
     * Update the current session id with a newly generated one
251
     *
252
     * To be refactored
253
     *
254
     * @param  bool $delete_old_session
255
     * @return bool
256
     **/
257
    public function regenerate_id($delete_old_session = false)
258
    {
259
        if (!$this->enableRegenerateId) {
260
            $success = true;
261
        } else {
262
            $success = session_regenerate_id($delete_old_session);
263
        }
264
265
        // Force updating cookie for session cookie
266
        if ($success) {
267
            $this->update_cookie();
268
        }
269
270
        return $success;
271
    }
272
273
    /**
274
     * Update cookie status for current session
275
     *
276
     * To be refactored
277
     * FIXME: how about $xoopsConfig['use_ssl'] is enabled?
278
     *
279
     * @param  string $sess_id session ID
280
     * @param  int    $expire  Time in seconds until a session expires
281
     * @return bool
282
     **/
283
    public function update_cookie($sess_id = null, $expire = null)
284
    {
285
        if (PHP_VERSION_ID < 70300) {
286
            global $xoopsConfig;
287
            $session_name = session_name();
288
            $session_expire = null !== $expire
289
                ? (int)$expire
290
                : (($xoopsConfig['use_mysession'] && $xoopsConfig['session_name'] != '')
291
                    ? $xoopsConfig['session_expire'] * 60
292
                    : ini_get('session.cookie_lifetime')
293
                );
294
            $session_id = empty($sess_id) ? session_id() : $sess_id;
295
            $cookieDomain = XOOPS_COOKIE_DOMAIN;
296
            if (2 > substr_count($cookieDomain, '.')) {
297
                $cookieDomain  = '.' . $cookieDomain ;
298
            }
299
300
            xoops_setcookie(
301
                $session_name,
302
                $session_id,
303
                $session_expire ? time() + $session_expire : 0,
304
                '/',
305
                $cookieDomain,
306
                (XOOPS_PROT === 'https://'),
307
                true
308
            );
309
        }
310
    }
311
}
312