Issues (1065)

Sources/Session.php (5 issues)

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 https://www.simplemachines.org
14
 * @copyright 2025 Simple Machines and individual contributors
15
 * @license https://www.simplemachines.org/about/smf/license.php BSD
16
 *
17
 * @version 2.1.5
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 $context, $modSettings, $boardurl, $sc, $smcFunc, $cache_enable;
29
30
	// Attempt to change a few PHP settings.
31
	@ini_set('session.use_cookies', true);
0 ignored issues
show
true of type true is incompatible with the type string expected by parameter $value of ini_set(). ( Ignorable by Annotation )

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

31
	@ini_set('session.use_cookies', /** @scrutinizer ignore-type */ true);
Loading history...
32
	@ini_set('session.use_only_cookies', false);
0 ignored issues
show
false of type false is incompatible with the type string expected by parameter $value of ini_set(). ( Ignorable by Annotation )

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

32
	@ini_set('session.use_only_cookies', /** @scrutinizer ignore-type */ false);
Loading history...
33
	@ini_set('url_rewriter.tags', '');
34
	@ini_set('session.use_trans_sid', false);
35
	@ini_set('arg_separator.output', '&amp;');
36
37
	// Allows mods to change/add PHP settings
38
	call_integration_hook('integrate_load_session');
39
40
	if (!empty($modSettings['globalCookies']))
41
	{
42
		$parsed_url = parse_iri($boardurl);
43
44
		if (preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
45
			@ini_set('session.cookie_domain', '.' . $parts[1]);
46
	}
47
	// @todo Set the session cookie path?
48
49
	// If it's already been started... probably best to skip this.
50
	if ((ini_get('session.auto_start') == 1 && !empty($modSettings['databaseSession_enable'])) || session_id() == '')
51
	{
52
		// Attempt to end the already-started session.
53
		if (ini_get('session.auto_start') == 1)
54
			session_write_close();
55
56
		// This is here to stop people from using bad junky PHPSESSIDs.
57
		if (isset($_REQUEST[session_name()]) && preg_match('~^[A-Za-z0-9,-]{16,64}$~', $_REQUEST[session_name()]) == 0 && !isset($_COOKIE[session_name()]))
58
		{
59
			$session_id = md5(md5('smf_sess_' . time()) . $smcFunc['random_int']());
60
			$_REQUEST[session_name()] = $session_id;
61
			$_GET[session_name()] = $session_id;
62
			$_POST[session_name()] = $session_id;
63
		}
64
65
		// Use database sessions? (they don't work in 4.1.x!)
66
		if (!empty($modSettings['databaseSession_enable']))
67
		{
68
			@ini_set('session.serialize_handler', 'php_serialize');
69
			if (ini_get('session.serialize_handler') != 'php_serialize')
70
				@ini_set('session.serialize_handler', 'php');
71
72
			$context['session_handler'] = new SmfSessionHandler();
73
			session_set_save_handler($context['session_handler'], true);
74
75
			@ini_set('session.gc_probability', '1');
76
		}
77
		elseif (ini_get('session.gc_maxlifetime') <= 1440 && !empty($modSettings['databaseSession_lifetime']))
78
			@ini_set('session.gc_maxlifetime', max($modSettings['databaseSession_lifetime'], 60));
79
80
		// Use cache setting sessions?
81
		if (empty($modSettings['databaseSession_enable']) && !empty($cache_enable) && php_sapi_name() != 'cli')
82
			call_integration_hook('integrate_session_handlers');
83
84
		session_start();
85
86
		// Change it so the cache settings are a little looser than default.
87
		if (!empty($modSettings['databaseSession_loose']))
88
			header('cache-control: private');
89
	}
90
91
	// Set the randomly generated code.
92
	if (!isset($_SESSION['session_var']))
93
	{
94
		$_SESSION['session_value'] = md5(session_id() . $smcFunc['random_int']());
95
		$_SESSION['session_var'] = substr(preg_replace('~^\d+~', '', sha1($smcFunc['random_int']() . session_id() . $smcFunc['random_int']())), 0, $smcFunc['random_int'](7, 12));
96
	}
97
	$sc = $_SESSION['session_value'];
98
}
99
100
/**
101
 * Class SmfSessionHandler
102
 *
103
 * An implementation of the SessionHandler
104
 *
105
 * Note: To support PHP 8.x, we use the attribute ReturnTypeWillChange. When
106
 * 8.1 is the miniumn, this can be removed.
107
 *
108
 * Note: To support PHP 7.x, we do not use type hints as SessionHandlerInterface
109
 * does not have them.
110
 */
111
class SmfSessionHandler extends SessionHandler implements SessionHandlerInterface, SessionIdInterface
112
{
113
	/**
114
	 * Implementation of SessionHandler::open() replacing the standard open handler.
115
	 * It simply returns true.
116
	 *
117
	 * @param string $path The path to save the session to
118
	 * @param string $name The name of the session
119
	 * @return boolean Always returns true
120
	 */
121
	function open(/*PHP 8.0 string*/$path, /*PHP 8.0 string*/$name): bool
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
122
	{
123
		return true;
124
	}
125
126
	/**
127
	 * Implementation of SessionHandler::close() replacing the standard close handler.
128
	 * It simply returns true.
129
	 *
130
	 * @return boolean Always returns true
131
	 */
132
	public function close(): bool
133
	{
134
		return true;
135
	}
136
137
	/**
138
	 * Implementation of SessionHandler::read() replacing the standard read handler.
139
	 *
140
	 * @param string $id The session ID
141
	 * @return string The session data
142
	 */
143
	#[\ReturnTypeWillChange]
144
	public function read(/*PHP 8.0 string*/$id)/*PHP 8.0: string|false*/
145
	{
146
		global $smcFunc;
147
148
		if (!$this->isValidSessionID($id))
149
			return '';
150
151
		// Look for it in the database.
152
		$result = $smcFunc['db_query']('', '
153
			SELECT data
154
			FROM {db_prefix}sessions
155
			WHERE session_id = {string:session_id}
156
			LIMIT 1',
157
			array(
158
				'session_id' => $id,
159
			)
160
		);
161
		list ($sess_data) = $smcFunc['db_fetch_row']($result);
162
		$smcFunc['db_free_result']($result);
163
164
		return $sess_data != null ? $sess_data : '';
165
	}
166
167
	/**
168
	 * Implementation of SessionHandler::write() replacing the standard write handler.
169
	 *
170
	 * @param string $id The session ID
171
	 * @param string $data The data to write to the session
172
	 * @return boolean Whether the info was successfully written
173
	 */
174
	#[\ReturnTypeWillChange]
175
	public function write(/*PHP 8.0 string*/$id,/*PHP 8.0 string */ $data): bool
176
	{
177
		global $smcFunc;
178
179
		if (!$this->isValidSessionID($id))
180
			return false;
181
182
		// If an insert fails due to a dupe, replace the existing session...
183
		$session_update = $smcFunc['db_insert']('replace',
0 ignored issues
show
The assignment to $session_update is dead and can be removed.
Loading history...
184
			'{db_prefix}sessions',
185
			array('session_id' => 'string', 'data' => 'string', 'last_update' => 'int'),
186
			array($id, $data, time()),
187
			array('session_id')
188
		);
189
190
		return ($smcFunc['db_affected_rows']() == 0 ? false : true);
191
	}
192
193
	/**
194
	 * Implementation of SessionHandler::destroy() replacing the standard destroy handler.
195
	 *
196
	 * @param string $session_id The session ID
197
	 * @return boolean Whether the session was successfully destroyed
198
	 */
199
	public function destroy(/*PHP 8.0 string*/$id): bool
200
	{
201
		global $smcFunc;
202
203
		if (!$this->isValidSessionID($id))
204
			return false;
205
206
		// Just delete the row...
207
		$smcFunc['db_query']('', '
208
			DELETE FROM {db_prefix}sessions
209
			WHERE session_id = {string:session_id}',
210
			array(
211
				'session_id' => $id,
212
			)
213
		);
214
215
		return true;
216
	}
217
218
	/**
219
	 * Implementation of SessionHandler::GC() replacing the standard gc handler.
220
	 * Callback for garbage collection.
221
	 *
222
	 * @param int $max_lifetime The maximum lifetime (in seconds) - prevents deleting of sessions older than this
223
	 * @return boolean Whether the option was successful
224
	 */
225
	#[\ReturnTypeWillChange]
226
	public function gc(/*PHP 8.0 int*/$max_lifetime)/*PHP 8.1 : int|false*/
227
	{
228
		global $modSettings, $smcFunc;
229
230
		// Just set to the default or lower?  Ignore it for a higher value. (hopefully)
231
		if (!empty($modSettings['databaseSession_lifetime']) && ($max_lifetime <= 1440 || $modSettings['databaseSession_lifetime'] > $max_lifetime))
232
			$max_lifetime = max($modSettings['databaseSession_lifetime'], 60);
233
234
		// Clean up after yerself ;).
235
		$session_update = $smcFunc['db_query']('', '
0 ignored issues
show
The assignment to $session_update is dead and can be removed.
Loading history...
236
			DELETE FROM {db_prefix}sessions
237
			WHERE last_update < {int:last_update}',
238
			array(
239
				'last_update' => time() - $max_lifetime,
240
			)
241
		);
242
243
		return $smcFunc['db_affected_rows']();
244
	}
245
246
	/**
247
	 * Validates a given string conforms to our testing for a valid session id.
248
	 *
249
	 * @param string $id The session ID
250
	 * @return boolean Whether the string is valid format or not
251
	 */
252
	private function isValidSessionID(string $id): bool
253
	{
254
		return preg_match('~^[A-Za-z0-9,-]{16,64}$~', $id) === 1;
255
	}
256
}
257
258
?>