Completed
Push — release-2.1 ( ecae71...61d523 )
by Colin
11:30
created

Session.php ➔ sessionWrite()   C

Complexity

Conditions 7
Paths 21

Size

Total Lines 48
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 25
nc 21
nop 2
dl 0
loc 48
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
/**
4
 *  Implementation of PHP's session API.
5
 * 	What it does:
6
 * 	- it handles the session data in the database (more scalable.)
7
 * 	- it uses the databaseSession_lifetime setting for garbage collection.
8
 * 	- the custom session handler is set by loadSession().
9
 *
10
 * Simple Machines Forum (SMF)
11
 *
12
 * @package SMF
13
 * @author Simple Machines http://www.simplemachines.org
14
 * @copyright 2018 Simple Machines and individual contributors
15
 * @license http://www.simplemachines.org/about/smf/license.php BSD
16
 *
17
 * @version 2.1 Beta 4
18
 */
19
20
if (!defined('SMF'))
21
	die('No direct access...');
22
23
/**
24
 * Attempt to start the session, unless it already has been.
25
 */
26
function loadSession()
27
{
28
	global $modSettings, $boardurl, $sc;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $sc. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
29
30
	// Attempt to change a few PHP settings.
31
	@ini_set('session.use_cookies', true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
32
	@ini_set('session.use_only_cookies', false);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
33
	@ini_set('url_rewriter.tags', '');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
34
	@ini_set('session.use_trans_sid', false);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
35
	@ini_set('arg_separator.output', '&amp;');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
36
37
	if (!empty($modSettings['globalCookies']))
38
	{
39
		$parsed_url = parse_url($boardurl);
40
41
		if (preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
42
			@ini_set('session.cookie_domain', '.' . $parts[1]);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
43
	}
44
	// @todo Set the session cookie path?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
45
46
	// If it's already been started... probably best to skip this.
47
	if ((ini_get('session.auto_start') == 1 && !empty($modSettings['databaseSession_enable'])) || session_id() == '')
48
	{
49
		// Attempt to end the already-started session.
50
		if (ini_get('session.auto_start') == 1)
51
			session_write_close();
52
53
		// This is here to stop people from using bad junky PHPSESSIDs.
54
		if (isset($_REQUEST[session_name()]) && preg_match('~^[A-Za-z0-9,-]{16,64}$~', $_REQUEST[session_name()]) == 0 && !isset($_COOKIE[session_name()]))
55
		{
56
			$session_id = md5(md5('smf_sess_' . time()) . mt_rand());
57
			$_REQUEST[session_name()] = $session_id;
58
			$_GET[session_name()] = $session_id;
59
			$_POST[session_name()] = $session_id;
60
		}
61
62
		// Use database sessions? (they don't work in 4.1.x!)
63
		if (!empty($modSettings['databaseSession_enable']))
64
		{
65
			@ini_set('session.serialize_handler', 'php_serialize');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
66
			if (ini_get('session.serialize_handler') != 'php_serialize')
67
				@ini_set('session.serialize_handler', 'php');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
68
			session_set_save_handler('sessionOpen', 'sessionClose', 'sessionRead', 'sessionWrite', 'sessionDestroy', 'sessionGC');
69
			@ini_set('session.gc_probability', '1');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
70
		}
71
		elseif (ini_get('session.gc_maxlifetime') <= 1440 && !empty($modSettings['databaseSession_lifetime']))
72
			@ini_set('session.gc_maxlifetime', max($modSettings['databaseSession_lifetime'], 60));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
73
74
		// Use cache setting sessions?
75
		if (empty($modSettings['databaseSession_enable']) && !empty($modSettings['cache_enable']) && php_sapi_name() != 'cli')
76
			call_integration_hook('integrate_session_handlers');
77
78
		session_start();
79
80
		// Change it so the cache settings are a little looser than default.
81
		if (!empty($modSettings['databaseSession_loose']))
82
			header('cache-control: private');
83
	}
84
85
	// Set the randomly generated code.
86 View Code Duplication
	if (!isset($_SESSION['session_var']))
87
	{
88
		$_SESSION['session_value'] = md5(session_id() . mt_rand());
89
		$_SESSION['session_var'] = substr(preg_replace('~^\d+~', '', sha1(mt_rand() . session_id() . mt_rand())), 0, mt_rand(7, 12));
90
	}
91
	$sc = $_SESSION['session_value'];
92
}
93
94
/**
95
 * Implementation of sessionOpen() replacing the standard open handler.
96
 * It simply returns true.
97
 *
98
 * @param string $save_path The path to save the session to
99
 * @param string $session_name The name of the session
100
 * @return boolean Always returns true
101
 */
102
function sessionOpen($save_path, $session_name)
0 ignored issues
show
Unused Code introduced by
The parameter $save_path is not used and could be removed.

This check looks from 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 $session_name is not used and could be removed.

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

Loading history...
103
{
104
	return true;
105
}
106
107
/**
108
 * Implementation of sessionClose() replacing the standard close handler.
109
 * It simply returns true.
110
 *
111
 * @return boolean Always returns true
112
 */
113
function sessionClose()
114
{
115
	return true;
116
}
117
118
/**
119
 * Implementation of sessionRead() replacing the standard read handler.
120
 *
121
 * @param string $session_id The session ID
122
 * @return string The session data
123
 */
124
function sessionRead($session_id)
125
{
126
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
127
128
	if (preg_match('~^[A-Za-z0-9,-]{16,64}$~', $session_id) == 0)
129
		return '';
130
131
	// Look for it in the database.
132
	$result = $smcFunc['db_query']('', '
133
		SELECT data
134
		FROM {db_prefix}sessions
135
		WHERE session_id = {string:session_id}
136
		LIMIT 1',
137
		array(
138
			'session_id' => $session_id,
139
		)
140
	);
141
	list ($sess_data) = $smcFunc['db_fetch_row']($result);
142
	$smcFunc['db_free_result']($result);
143
144
	return $sess_data != null ? $sess_data : '';
145
}
146
147
/**
148
 * Implementation of sessionWrite() replacing the standard write handler.
149
 *
150
 * @param string $session_id The session ID
151
 * @param string $data The data to write to the session
152
 * @return boolean Whether the info was successfully written
153
 */
154
function sessionWrite($session_id, $data)
155
{
156
	global $smcFunc, $db_connection, $db_server, $db_name, $db_user, $db_passwd;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
157
	global $db_prefix, $db_persist, $db_port, $db_mb4;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
158
159
	if (preg_match('~^[A-Za-z0-9,-]{16,64}$~', $session_id) == 0)
160
		return false;
161
162
	// php < 7.0 need this
163
	if (empty($db_connection))
164
	{
165
		$db_options = array();
166
167
		// Add in the port if needed
168
		if (!empty($db_port))
169
			$db_options['port'] = $db_port;
170
171
		if (!empty($db_mb4))
172
		$db_options['db_mb4'] = $db_mb4;
173
174
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
175
176
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
177
	}
178
179
	// First try to update an existing row...
180
	$smcFunc['db_query']('', '
181
		UPDATE {db_prefix}sessions
182
		SET data = {string:data}, last_update = {int:last_update}
183
		WHERE session_id = {string:session_id}',
184
		array(
185
			'last_update' => time(),
186
			'data' => $data,
187
			'session_id' => $session_id,
188
		)
189
	);
190
191
	// If that didn't work, try inserting a new one.
192
	if ($smcFunc['db_affected_rows']() == 0)
193
		$smcFunc['db_insert']('ignore',
194
			'{db_prefix}sessions',
195
			array('session_id' => 'string', 'data' => 'string', 'last_update' => 'int'),
196
			array($session_id, $data, time()),
197
			array('session_id')
198
		);
199
200
	return ($smcFunc['db_affected_rows']() == 0 ? false : true);
201
}
202
203
/**
204
 * Implementation of sessionDestroy() replacing the standard destroy handler.
205
 *
206
 * @param string $session_id The session ID
207
 * @return boolean Whether the session was successfully destroyed
208
 */
209
function sessionDestroy($session_id)
210
{
211
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
212
213
	if (preg_match('~^[A-Za-z0-9,-]{16,64}$~', $session_id) == 0)
214
		return false;
215
216
	// Just delete the row...
217
	$smcFunc['db_query']('', '
218
		DELETE FROM {db_prefix}sessions
219
		WHERE session_id = {string:session_id}',
220
		array(
221
			'session_id' => $session_id,
222
		)
223
	);
224
225
	return true;
226
}
227
228
/**
229
 * Implementation of sessionGC() replacing the standard gc handler.
230
 * Callback for garbage collection.
231
 *
232
 * @param int $max_lifetime The maximum lifetime (in seconds) - prevents deleting of sessions older than this
233
 * @return boolean Whether the option was successful
234
 */
235
function sessionGC($max_lifetime)
236
{
237
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
238
239
	// Just set to the default or lower?  Ignore it for a higher value. (hopefully)
240
	if (!empty($modSettings['databaseSession_lifetime']) && ($max_lifetime <= 1440 || $modSettings['databaseSession_lifetime'] > $max_lifetime))
241
		$max_lifetime = max($modSettings['databaseSession_lifetime'], 60);
242
243
	// Clean up after yerself ;).
244
	$smcFunc['db_query']('', '
245
		DELETE FROM {db_prefix}sessions
246
		WHERE last_update < {int:last_update}',
247
		array(
248
			'last_update' => time() - $max_lifetime,
249
		)
250
	);
251
252
	return ($smcFunc['db_affected_rows']() == 0 ? false : true);
253
}
254
255
?>