Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 492 lines (451 sloc) 16.651 kb
710f2f3 Elliott Foster First commit - D7 Core
authored
1 <?php
2 // $Id: session.inc,v 1.92 2010/11/13 17:40:09 webchick Exp $
3
4 /**
5 * @file
6 * User session handling functions.
7 *
8 * The user-level session storage handlers:
9 * - _drupal_session_open()
10 * - _drupal_session_close()
11 * - _drupal_session_read()
12 * - _drupal_session_write()
13 * - _drupal_session_destroy()
14 * - _drupal_session_garbage_collection()
15 * are assigned by session_set_save_handler() in bootstrap.inc and are called
16 * automatically by PHP. These functions should not be called directly. Session
17 * data should instead be accessed via the $_SESSION superglobal.
18 */
19
20 /**
21 * Session handler assigned by session_set_save_handler().
22 *
23 * This function is used to handle any initialization, such as file paths or
24 * database connections, that is needed before accessing session data. Drupal
25 * does not need to initialize anything in this function.
26 *
27 * This function should not be called directly.
28 *
29 * @return
30 * This function will always return TRUE.
31 */
32 function _drupal_session_open() {
33 return TRUE;
34 }
35
36 /**
37 * Session handler assigned by session_set_save_handler().
38 *
39 * This function is used to close the current session. Because Drupal stores
40 * session data in the database immediately on write, this function does
41 * not need to do anything.
42 *
43 * This function should not be called directly.
44 *
45 * @return
46 * This function will always return TRUE.
47 */
48 function _drupal_session_close() {
49 return TRUE;
50 }
51
52 /**
53 * Session handler assigned by session_set_save_handler().
54 *
55 * This function will be called by PHP to retrieve the current user's
56 * session data, which is stored in the database. It also loads the
57 * current user's appropriate roles into the user object.
58 *
59 * This function should not be called directly. Session data should
60 * instead be accessed via the $_SESSION superglobal.
61 *
62 * @param $sid
63 * Session ID.
64 *
65 * @return
66 * Either an array of the session data, or an empty string, if no data
67 * was found or the user is anonymous.
68 */
69 function _drupal_session_read($sid) {
70 global $user, $is_https;
71
72 // Write and Close handlers are called after destructing objects
73 // since PHP 5.0.5.
74 // Thus destructors can use sessions but session handler can't use objects.
75 // So we are moving session closure before destructing objects.
76 drupal_register_shutdown_function('session_write_close');
77
78 // Handle the case of first time visitors and clients that don't store
79 // cookies (eg. web crawlers).
80 $insecure_session_name = substr(session_name(), 1);
81 if (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name])) {
82 $user = drupal_anonymous_user();
83 return '';
84 }
85
86 // Otherwise, if the session is still active, we have a record of the
87 // client's session in the database. If it's HTTPS then we are either have
88 // a HTTPS session or we are about to log in so we check the sessions table
89 // for an anonymous session with the non-HTTPS-only cookie.
90 if ($is_https) {
91 $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject();
92 if (!$user) {
93 if (isset($_COOKIE[$insecure_session_name])) {
94 $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array(
95 ':sid' => $_COOKIE[$insecure_session_name]))
96 ->fetchObject();
97 }
98 }
99 }
100 else {
101 $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject();
102 }
103
104 // We found the client's session record and they are an authenticated,
105 // active user.
106 if ($user && $user->uid > 0 && $user->status == 1) {
107 // This is done to unserialize the data member of $user.
108 $user->data = unserialize($user->data);
109
110 // Add roles element to $user.
111 $user->roles = array();
112 $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
113 $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1);
114 }
115 elseif ($user) {
116 // The user is anonymous or blocked. Only preserve two fields from the
117 // {sessions} table.
118 $account = drupal_anonymous_user();
119 $account->session = $user->session;
120 $account->timestamp = $user->timestamp;
121 $user = $account;
122 }
123 else {
124 // The session has expired.
125 $user = drupal_anonymous_user();
126 $user->session = '';
127 }
128
129 // Store the session that was read for comparison in _drupal_session_write().
130 $last_read = &drupal_static('drupal_session_last_read');
131 $last_read = array(
132 'sid' => $sid,
133 'value' => $user->session,
134 );
135
136 return $user->session;
137 }
138
139 /**
140 * Session handler assigned by session_set_save_handler().
141 *
142 * This function will be called by PHP to store the current user's
143 * session, which Drupal saves to the database.
144 *
145 * This function should not be called directly. Session data should
146 * instead be accessed via the $_SESSION superglobal.
147 *
148 * @param $sid
149 * Session ID.
150 * @param $value
151 * Serialized array of the session data.
152 *
153 * @return
154 * This function will always return TRUE.
155 */
156 function _drupal_session_write($sid, $value) {
157 global $user, $is_https;
158
159 // The exception handler is not active at this point, so we need to do it
160 // manually.
161 try {
162 if (!drupal_save_session()) {
163 // We don't have anything to do if we are not allowed to save the session.
164 return;
165 }
166
167 // Check whether $_SESSION has been changed in this request.
168 $last_read = &drupal_static('drupal_session_last_read');
169 $is_changed = !isset($last_read) || $last_read['sid'] != $sid || $last_read['value'] !== $value;
170
171 // For performance reasons, do not update the sessions table, unless
172 // $_SESSION has changed or more than 180 has passed since the last update.
173 if ($is_changed || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) {
174 // Either ssid or sid or both will be added from $key below.
175 $fields = array(
176 'uid' => $user->uid,
177 'cache' => isset($user->cache) ? $user->cache : 0,
178 'hostname' => ip_address(),
179 'session' => $value,
180 'timestamp' => REQUEST_TIME,
181 );
182
183 // Use the session ID as 'sid' and an empty string as 'ssid' by default.
184 // _drupal_session_read() does not allow empty strings so that's a safe
185 // default.
186 $key = array('sid' => $sid, 'ssid' => '');
187 // On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
188 if ($is_https) {
189 $key['ssid'] = $sid;
190 // The "secure pages" setting allows a site to simultaneously use both
191 // secure and insecure session cookies. If enabled and both cookies are
192 // presented then use both keys.
193 if (variable_get('https', FALSE)) {
194 $insecure_session_name = substr(session_name(), 1);
195 if (isset($_COOKIE[$insecure_session_name])) {
196 $key['sid'] = $_COOKIE[$insecure_session_name];
197 }
198 }
199 }
200
201 db_merge('sessions')
202 ->key($key)
203 ->fields($fields)
204 ->execute();
205 }
206
207 // Likewise, do not update access time more than once per 180 seconds.
208 if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) {
209 db_update('users')
210 ->fields(array(
211 'access' => REQUEST_TIME
212 ))
213 ->condition('uid', $user->uid)
214 ->execute();
215 }
216
217 return TRUE;
218 }
219 catch (Exception $exception) {
220 require_once DRUPAL_ROOT . '/includes/errors.inc';
221 // If we are displaying errors, then do so with no possibility of a further
222 // uncaught exception being thrown.
223 if (error_displayable()) {
224 print '<h1>Uncaught exception thrown in session handler.</h1>';
225 print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />';
226 }
227 return FALSE;
228 }
229 }
230
231 /**
232 * Initializes the session handler, starting a session if needed.
233 */
234 function drupal_session_initialize() {
235 global $user, $is_https;
236
237 session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
238
239 // We use !empty() in the following check to ensure that blank session IDs
240 // are not valid.
241 if (!empty($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) {
242 // If a session cookie exists, initialize the session. Otherwise the
243 // session is only started on demand in drupal_session_commit(), making
244 // anonymous users not use a session cookie unless something is stored in
245 // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
246 drupal_session_start();
247 if (!empty($user->uid) || !empty($_SESSION)) {
248 drupal_page_is_cacheable(FALSE);
249 }
250 }
251 else {
252 // Set a session identifier for this request. This is necessary because
253 // we lazily start sessions at the end of this request, and some
254 // processes (like drupal_get_token()) needs to know the future
255 // session ID in advance.
256 $user = drupal_anonymous_user();
257 // Less random sessions (which are much faster to generate) are used for
258 // anonymous users than are generated in drupal_session_regenerate() when
259 // a user becomes authenticated.
260 session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE)));
261 }
262 date_default_timezone_set(drupal_get_user_timezone());
263 }
264
265 /**
266 * Forcefully starts a session, preserving already set session data.
267 *
268 * @ingroup php_wrappers
269 */
270 function drupal_session_start() {
271 // Command line clients do not support cookies nor sessions.
272 if (!drupal_session_started() && !drupal_is_cli()) {
273 // Save current session data before starting it, as PHP will destroy it.
274 $session_data = isset($_SESSION) ? $_SESSION : NULL;
275
276 session_start();
277 drupal_session_started(TRUE);
278
279 // Restore session data.
280 if (!empty($session_data)) {
281 $_SESSION += $session_data;
282 }
283 }
284 }
285
286 /**
287 * Commits the current session, if necessary.
288 *
289 * If an anonymous user already have an empty session, destroy it.
290 */
291 function drupal_session_commit() {
292 global $user;
293
294 if (!drupal_save_session()) {
295 // We don't have anything to do if we are not allowed to save the session.
296 return;
297 }
298
299 if (empty($user->uid) && empty($_SESSION)) {
300 // There is no session data to store, destroy the session if it was
301 // previously started.
302 if (drupal_session_started()) {
303 session_destroy();
304 }
305 }
306 else {
307 // There is session data to store. Start the session if it is not already
308 // started.
309 if (!drupal_session_started()) {
310 drupal_session_start();
311 }
312 // Write the session data.
313 session_write_close();
314 }
315 }
316
317 /**
318 * Returns whether a session has been started.
319 */
320 function drupal_session_started($set = NULL) {
321 static $session_started = FALSE;
322 if (isset($set)) {
323 $session_started = $set;
324 }
325 return $session_started && session_id();
326 }
327
328 /**
329 * Called when an anonymous user becomes authenticated or vice-versa.
330 *
331 * @ingroup php_wrappers
332 */
333 function drupal_session_regenerate() {
334 global $user, $is_https;
335 if ($is_https && variable_get('https', FALSE)) {
336 $insecure_session_name = substr(session_name(), 1);
337 if (isset($_COOKIE[$insecure_session_name])) {
338 $old_insecure_session_id = $_COOKIE[$insecure_session_name];
339 }
340 $params = session_get_cookie_params();
341 $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55));
342 // If a session cookie lifetime is set, the session will expire
343 // $params['lifetime'] seconds from the current request. If it is not set,
344 // it will expire when the browser is closed.
345 $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
346 setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
347 $_COOKIE[$insecure_session_name] = $session_id;
348 }
349
350 if (drupal_session_started()) {
351 $old_session_id = session_id();
352 }
353 session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)));
354
355 if (isset($old_session_id)) {
356 $params = session_get_cookie_params();
357 $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
358 setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
359 $fields = array('sid' => session_id());
360 if ($is_https) {
361 $fields['ssid'] = session_id();
362 // If the "secure pages" setting is enabled, use the newly-created
363 // insecure session identifier as the regenerated sid.
364 if (variable_get('https', FALSE)) {
365 $fields['sid'] = $session_id;
366 }
367 }
368 db_update('sessions')
369 ->fields($fields)
370 ->condition($is_https ? 'ssid' : 'sid', $old_session_id)
371 ->execute();
372 }
373 elseif (isset($old_insecure_session_id)) {
374 // If logging in to the secure site, and there was no active session on the
375 // secure site but a session was active on the insecure site, update the
376 // insecure session with the new session identifiers.
377 db_update('sessions')
378 ->fields(array('sid' => $session_id, 'ssid' => session_id()))
379 ->condition('sid', $old_insecure_session_id)
380 ->execute();
381 }
382 else {
383 // Start the session when it doesn't exist yet.
384 // Preserve the logged in user, as it will be reset to anonymous
385 // by _drupal_session_read.
386 $account = $user;
387 drupal_session_start();
388 $user = $account;
389 }
390 date_default_timezone_set(drupal_get_user_timezone());
391 }
392
393 /**
394 * Session handler assigned by session_set_save_handler().
395 *
396 * Cleans up a specific session.
397 *
398 * @param $sid
399 * Session ID.
400 */
401 function _drupal_session_destroy($sid) {
402 global $user, $is_https;
403
404 // Delete session data.
405 db_delete('sessions')
406 ->condition($is_https ? 'ssid' : 'sid', $sid)
407 ->execute();
408
409 // Reset $_SESSION and $user to prevent a new session from being started
410 // in drupal_session_commit().
411 $_SESSION = array();
412 $user = drupal_anonymous_user();
413
414 // Unset the session cookies.
415 _drupal_session_delete_cookie(session_name());
416 if ($is_https) {
417 _drupal_session_delete_cookie(substr(session_name(), 1), TRUE);
418 }
419 }
420
421 /**
422 * Deletes the session cookie.
423 *
424 * @param $name
425 * Name of session cookie to delete.
426 * @param $force_insecure
427 * Force cookie to be insecure.
428 */
429 function _drupal_session_delete_cookie($name, $force_insecure = FALSE) {
430 if (isset($_COOKIE[$name])) {
431 $params = session_get_cookie_params();
432 setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']);
433 unset($_COOKIE[$name]);
434 }
435 }
436
437 /**
438 * Ends a specific user's session(s).
439 *
440 * @param $uid
441 * User ID.
442 */
443 function drupal_session_destroy_uid($uid) {
444 db_delete('sessions')
445 ->condition('uid', $uid)
446 ->execute();
447 }
448
449 /**
450 * Session handler assigned by session_set_save_handler().
451 *
452 * Cleans up stalled sessions.
453 *
454 * @param $lifetime
455 * The value of session.gc_maxlifetime, passed by PHP.
456 * Sessions not updated for more than $lifetime seconds will be removed.
457 */
458 function _drupal_session_garbage_collection($lifetime) {
459 // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
460 // value. For example, if you want user sessions to stay in your database
461 // for three weeks before deleting them, you need to set gc_maxlifetime
462 // to '1814400'. At that value, only after a user doesn't log in after
463 // three weeks (1814400 seconds) will his/her session be removed.
464 db_delete('sessions')
465 ->condition('timestamp', REQUEST_TIME - $lifetime, '<')
466 ->execute();
467 return TRUE;
468 }
469
470 /**
471 * Determines whether to save session data of the current request.
472 *
473 * This function allows the caller to temporarily disable writing of
474 * session data, should the request end while performing potentially
475 * dangerous operations, such as manipulating the global $user object.
476 * See http://drupal.org/node/218104 for usage.
477 *
478 * @param $status
479 * Disables writing of session data when FALSE, (re-)enables
480 * writing when TRUE.
481 *
482 * @return
483 * FALSE if writing session data has been disabled. Otherwise, TRUE.
484 */
485 function drupal_save_session($status = NULL) {
486 $save_session = &drupal_static(__FUNCTION__, TRUE);
487 if (isset($status)) {
488 $save_session = $status;
489 }
490 return $save_session;
491 }
Something went wrong with that request. Please try again.