Project

General

Profile

Paste
Download (18.6 KB) Statistics
| Branch: | Revision:

root / drupal7 / includes / session.inc @ 8916d7ec

1
<?php
2

    
3
/**
4
 * @file
5
 * User session handling functions.
6
 *
7
 * The user-level session storage handlers:
8
 * - _drupal_session_open()
9
 * - _drupal_session_close()
10
 * - _drupal_session_read()
11
 * - _drupal_session_write()
12
 * - _drupal_session_destroy()
13
 * - _drupal_session_garbage_collection()
14
 * are assigned by session_set_save_handler() in bootstrap.inc and are called
15
 * automatically by PHP. These functions should not be called directly. Session
16
 * data should instead be accessed via the $_SESSION superglobal.
17
 */
18

    
19
/**
20
 * Session handler assigned by session_set_save_handler().
21
 *
22
 * This function is used to handle any initialization, such as file paths or
23
 * database connections, that is needed before accessing session data. Drupal
24
 * does not need to initialize anything in this function.
25
 *
26
 * This function should not be called directly.
27
 *
28
 * @return
29
 *   This function will always return TRUE.
30
 */
31
function _drupal_session_open() {
32
  return TRUE;
33
}
34

    
35
/**
36
 * Session handler assigned by session_set_save_handler().
37
 *
38
 * This function is used to close the current session. Because Drupal stores
39
 * session data in the database immediately on write, this function does
40
 * not need to do anything.
41
 *
42
 * This function should not be called directly.
43
 *
44
 * @return
45
 *   This function will always return TRUE.
46
 */
47
function _drupal_session_close() {
48
  return TRUE;
49
}
50

    
51
/**
52
 * Reads an entire session from the database (internal use only).
53
 *
54
 * Also initializes the $user object for the user associated with the session.
55
 * This function is registered with session_set_save_handler() to support
56
 * database-backed sessions. It is called on every page load when PHP sets
57
 * up the $_SESSION superglobal.
58
 *
59
 * This function is an internal function and must not be called directly.
60
 * Doing so may result in logging out the current user, corrupting session data
61
 * or other unexpected behavior. Session data must always be accessed via the
62
 * $_SESSION superglobal.
63
 *
64
 * @param $sid
65
 *   The session ID of the session to retrieve.
66
 *
67
 * @return
68
 *   The user's session, or an empty string if no session exists.
69
 */
70
function _drupal_session_read($sid) {
71
  global $user, $is_https;
72

    
73
  // Write and Close handlers are called after destructing objects
74
  // since PHP 5.0.5.
75
  // Thus destructors can use sessions but session handler can't use objects.
76
  // So we are moving session closure before destructing objects.
77
  drupal_register_shutdown_function('session_write_close');
78

    
79
  // Handle the case of first time visitors and clients that don't store
80
  // cookies (eg. web crawlers).
81
  $insecure_session_name = substr(session_name(), 1);
82
  if (empty($sid) || (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name]))) {
83
    $user = drupal_anonymous_user();
84
    return '';
85
  }
86

    
87
  // Otherwise, if the session is still active, we have a record of the
88
  // client's session in the database. If it's HTTPS then we are either have
89
  // a HTTPS session or we are about to log in so we check the sessions table
90
  // for an anonymous session with the non-HTTPS-only cookie.
91
  if ($is_https) {
92
    $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();
93
    if (!$user) {
94
      if (isset($_COOKIE[$insecure_session_name])) {
95
        $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(
96
        ':sid' => $_COOKIE[$insecure_session_name]))
97
        ->fetchObject();
98
      }
99
    }
100
  }
101
  else {
102
    $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();
103
  }
104

    
105
  // We found the client's session record and they are an authenticated,
106
  // active user.
107
  if ($user && $user->uid > 0 && $user->status == 1) {
108
    // This is done to unserialize the data member of $user.
109
    $user->data = unserialize($user->data);
110

    
111
    // Add roles element to $user.
112
    $user->roles = array();
113
    $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
114
    $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);
115
  }
116
  elseif ($user) {
117
    // The user is anonymous or blocked. Only preserve two fields from the
118
    // {sessions} table.
119
    $account = drupal_anonymous_user();
120
    $account->session = $user->session;
121
    $account->timestamp = $user->timestamp;
122
    $user = $account;
123
  }
124
  else {
125
    // The session has expired.
126
    $user = drupal_anonymous_user();
127
    $user->session = '';
128
  }
129

    
130
  // Store the session that was read for comparison in _drupal_session_write().
131
  $last_read = &drupal_static('drupal_session_last_read');
132
  $last_read = array(
133
    'sid' => $sid,
134
    'value' => $user->session,
135
  );
136

    
137
  return $user->session;
138
}
139

    
140
/**
141
 * Writes an entire session to the database (internal use only).
142
 *
143
 * This function is registered with session_set_save_handler() to support
144
 * database-backed sessions.
145
 *
146
 * This function is an internal function and must not be called directly.
147
 * Doing so may result in corrupted session data or other unexpected behavior.
148
 * Session data must always be accessed via the $_SESSION superglobal.
149
 *
150
 * @param $sid
151
 *   The session ID of the session to write to.
152
 * @param $value
153
 *   Session data to write as a serialized string.
154
 *
155
 * @return
156
 *   Always returns TRUE.
157
 */
158
function _drupal_session_write($sid, $value) {
159
  global $user, $is_https;
160

    
161
  // The exception handler is not active at this point, so we need to do it
162
  // manually.
163
  try {
164
    if (!drupal_save_session()) {
165
      // We don't have anything to do if we are not allowed to save the session.
166
      return TRUE;
167
    }
168

    
169
    // Check whether $_SESSION has been changed in this request.
170
    $last_read = &drupal_static('drupal_session_last_read');
171
    $is_changed = !isset($last_read) || $last_read['sid'] != $sid || $last_read['value'] !== $value;
172

    
173
    // For performance reasons, do not update the sessions table, unless
174
    // $_SESSION has changed or more than 180 has passed since the last update.
175
    if ($is_changed || !isset($user->timestamp) || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) {
176
      // Either ssid or sid or both will be added from $key below.
177
      $fields = array(
178
        'uid' => $user->uid,
179
        'cache' => isset($user->cache) ? $user->cache : 0,
180
        'hostname' => ip_address(),
181
        'session' => $value,
182
        'timestamp' => REQUEST_TIME,
183
      );
184

    
185
      // Use the session ID as 'sid' and an empty string as 'ssid' by default.
186
      // _drupal_session_read() does not allow empty strings so that's a safe
187
      // default.
188
      $key = array('sid' => $sid, 'ssid' => '');
189
      // On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
190
      if ($is_https) {
191
        $key['ssid'] = $sid;
192
        // The "secure pages" setting allows a site to simultaneously use both
193
        // secure and insecure session cookies. If enabled and both cookies are
194
        // presented then use both keys.
195
        if (variable_get('https', FALSE)) {
196
          $insecure_session_name = substr(session_name(), 1);
197
          if (isset($_COOKIE[$insecure_session_name])) {
198
            $key['sid'] = $_COOKIE[$insecure_session_name];
199
          }
200
        }
201
      }
202
      elseif (variable_get('https', FALSE)) {
203
        unset($key['ssid']);
204
      }
205

    
206
      db_merge('sessions')
207
        ->key($key)
208
        ->fields($fields)
209
        ->execute();
210
    }
211

    
212
    // Likewise, do not update access time more than once per 180 seconds.
213
    if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) {
214
      db_update('users')
215
        ->fields(array(
216
          'access' => REQUEST_TIME
217
        ))
218
        ->condition('uid', $user->uid)
219
        ->execute();
220
    }
221

    
222
    return TRUE;
223
  }
224
  catch (Exception $exception) {
225
    require_once DRUPAL_ROOT . '/includes/errors.inc';
226
    // If we are displaying errors, then do so with no possibility of a further
227
    // uncaught exception being thrown.
228
    if (error_displayable()) {
229
      print '<h1>Uncaught exception thrown in session handler.</h1>';
230
      print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />';
231
    }
232
    return FALSE;
233
  }
234
}
235

    
236
/**
237
 * Initializes the session handler, starting a session if needed.
238
 */
239
function drupal_session_initialize() {
240
  global $user, $is_https;
241

    
242
  session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
243

    
244
  // We use !empty() in the following check to ensure that blank session IDs
245
  // are not valid.
246
  if (!empty($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) {
247
    // If a session cookie exists, initialize the session. Otherwise the
248
    // session is only started on demand in drupal_session_commit(), making
249
    // anonymous users not use a session cookie unless something is stored in
250
    // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
251
    drupal_session_start();
252
    if (!empty($user->uid) || !empty($_SESSION)) {
253
      drupal_page_is_cacheable(FALSE);
254
    }
255
  }
256
  else {
257
    // Set a session identifier for this request. This is necessary because
258
    // we lazily start sessions at the end of this request, and some
259
    // processes (like drupal_get_token()) needs to know the future
260
    // session ID in advance.
261
    $GLOBALS['lazy_session'] = TRUE;
262
    $user = drupal_anonymous_user();
263
    // Less random sessions (which are much faster to generate) are used for
264
    // anonymous users than are generated in drupal_session_regenerate() when
265
    // a user becomes authenticated.
266
    session_id(drupal_random_key());
267
    if ($is_https && variable_get('https', FALSE)) {
268
      $insecure_session_name = substr(session_name(), 1);
269
      $session_id = drupal_random_key();
270
      $_COOKIE[$insecure_session_name] = $session_id;
271
    }
272
  }
273
  date_default_timezone_set(drupal_get_user_timezone());
274
}
275

    
276
/**
277
 * Starts a session forcefully, preserving already set session data.
278
 *
279
 * @ingroup php_wrappers
280
 */
281
function drupal_session_start() {
282
  // Command line clients do not support cookies nor sessions.
283
  if (!drupal_session_started() && !drupal_is_cli()) {
284
    // Save current session data before starting it, as PHP will destroy it.
285
    $session_data = isset($_SESSION) ? $_SESSION : NULL;
286

    
287
    session_start();
288
    drupal_session_started(TRUE);
289

    
290
    // Restore session data.
291
    if (!empty($session_data)) {
292
      $_SESSION += $session_data;
293
    }
294
  }
295
}
296

    
297
/**
298
 * Commits the current session, if necessary.
299
 *
300
 * If an anonymous user already have an empty session, destroy it.
301
 */
302
function drupal_session_commit() {
303
  global $user, $is_https;
304

    
305
  if (!drupal_save_session()) {
306
    // We don't have anything to do if we are not allowed to save the session.
307
    return;
308
  }
309

    
310
  if (empty($user->uid) && empty($_SESSION)) {
311
    // There is no session data to store, destroy the session if it was
312
    // previously started.
313
    if (drupal_session_started()) {
314
      session_destroy();
315
    }
316
  }
317
  else {
318
    // There is session data to store. Start the session if it is not already
319
    // started.
320
    if (!drupal_session_started()) {
321
      drupal_session_start();
322
      if ($is_https && variable_get('https', FALSE)) {
323
        $insecure_session_name = substr(session_name(), 1);
324
        $params = session_get_cookie_params();
325
        $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
326
        setcookie($insecure_session_name, $_COOKIE[$insecure_session_name], $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
327
      }
328
    }
329
    // Write the session data.
330
    session_write_close();
331
  }
332
}
333

    
334
/**
335
 * Returns whether a session has been started.
336
 */
337
function drupal_session_started($set = NULL) {
338
  static $session_started = FALSE;
339
  if (isset($set)) {
340
    $session_started = $set;
341
  }
342
  return $session_started && session_id();
343
}
344

    
345
/**
346
 * Called when an anonymous user becomes authenticated or vice-versa.
347
 *
348
 * @ingroup php_wrappers
349
 */
350
function drupal_session_regenerate() {
351
  global $user, $is_https;
352
  // Nothing to do if we are not allowed to change the session.
353
  if (!drupal_save_session()) {
354
    return;
355
  }
356

    
357
  if ($is_https && variable_get('https', FALSE)) {
358
    $insecure_session_name = substr(session_name(), 1);
359
    if (!isset($GLOBALS['lazy_session']) && isset($_COOKIE[$insecure_session_name])) {
360
      $old_insecure_session_id = $_COOKIE[$insecure_session_name];
361
    }
362
    $params = session_get_cookie_params();
363
    $session_id = drupal_random_key();
364
    // If a session cookie lifetime is set, the session will expire
365
    // $params['lifetime'] seconds from the current request. If it is not set,
366
    // it will expire when the browser is closed.
367
    $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
368
    setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
369
    $_COOKIE[$insecure_session_name] = $session_id;
370
  }
371

    
372
  if (drupal_session_started()) {
373
    $old_session_id = session_id();
374
    _drupal_session_regenerate_existing();
375
  }
376
  else {
377
    session_id(drupal_random_key());
378
  }
379

    
380
  if (isset($old_session_id)) {
381
    $params = session_get_cookie_params();
382
    $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
383
    setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
384
    $fields = array('sid' => session_id());
385
    if ($is_https) {
386
      $fields['ssid'] = session_id();
387
      // If the "secure pages" setting is enabled, use the newly-created
388
      // insecure session identifier as the regenerated sid.
389
      if (variable_get('https', FALSE)) {
390
        $fields['sid'] = $session_id;
391
      }
392
    }
393
    db_update('sessions')
394
      ->fields($fields)
395
      ->condition($is_https ? 'ssid' : 'sid', $old_session_id)
396
      ->execute();
397
  }
398
  elseif (isset($old_insecure_session_id)) {
399
    // If logging in to the secure site, and there was no active session on the
400
    // secure site but a session was active on the insecure site, update the
401
    // insecure session with the new session identifiers.
402
    db_update('sessions')
403
      ->fields(array('sid' => $session_id, 'ssid' => session_id()))
404
      ->condition('sid', $old_insecure_session_id)
405
      ->execute();
406
  }
407
  else {
408
    // Start the session when it doesn't exist yet.
409
    // Preserve the logged in user, as it will be reset to anonymous
410
    // by _drupal_session_read.
411
    $account = $user;
412
    drupal_session_start();
413
    $user = $account;
414
  }
415
  date_default_timezone_set(drupal_get_user_timezone());
416
}
417

    
418
/**
419
 * Regenerates an existing session.
420
 */
421
function _drupal_session_regenerate_existing() {
422
  global $user;
423
  // Preserve existing settings for the saving of sessions.
424
  $original_save_session_status = drupal_save_session();
425
  // Turn off saving of sessions.
426
  drupal_save_session(FALSE);
427
  session_write_close();
428
  drupal_session_started(FALSE);
429
  // Preserve the user object, as starting a new session will reset it.
430
  $original_user = $user;
431
  session_id(drupal_random_key());
432
  drupal_session_start();
433
  $user = $original_user;
434
  // Restore the original settings for the saving of sessions.
435
  drupal_save_session($original_save_session_status);
436
}
437

    
438
/**
439
 * Session handler assigned by session_set_save_handler().
440
 *
441
 * Cleans up a specific session.
442
 *
443
 * @param $sid
444
 *   Session ID.
445
 */
446
function _drupal_session_destroy($sid) {
447
  global $user, $is_https;
448

    
449
  // Nothing to do if we are not allowed to change the session.
450
  if (!drupal_save_session()) {
451
    return TRUE;
452
  }
453

    
454
  // Delete session data.
455
  db_delete('sessions')
456
    ->condition($is_https ? 'ssid' : 'sid', $sid)
457
    ->execute();
458

    
459
  // Reset $_SESSION and $user to prevent a new session from being started
460
  // in drupal_session_commit().
461
  $_SESSION = array();
462
  $user = drupal_anonymous_user();
463

    
464
  // Unset the session cookies.
465
  _drupal_session_delete_cookie(session_name());
466
  if ($is_https) {
467
    _drupal_session_delete_cookie(substr(session_name(), 1), FALSE);
468
  }
469
  elseif (variable_get('https', FALSE)) {
470
    _drupal_session_delete_cookie('S' . session_name(), TRUE);
471
  }
472

    
473
  return TRUE;
474
}
475

    
476
/**
477
 * Deletes the session cookie.
478
 *
479
 * @param $name
480
 *   Name of session cookie to delete.
481
 * @param boolean $secure
482
 *   Force the secure value of the cookie.
483
 */
484
function _drupal_session_delete_cookie($name, $secure = NULL) {
485
  global $is_https;
486
  if (isset($_COOKIE[$name]) || (!$is_https && $secure === TRUE)) {
487
    $params = session_get_cookie_params();
488
    if ($secure !== NULL) {
489
      $params['secure'] = $secure;
490
    }
491
    setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
492
    unset($_COOKIE[$name]);
493
  }
494
}
495

    
496
/**
497
 * Ends a specific user's session(s).
498
 *
499
 * @param $uid
500
 *   User ID.
501
 */
502
function drupal_session_destroy_uid($uid) {
503
  // Nothing to do if we are not allowed to change the session.
504
  if (!drupal_save_session()) {
505
    return;
506
  }
507

    
508
  db_delete('sessions')
509
    ->condition('uid', $uid)
510
    ->execute();
511
}
512

    
513
/**
514
 * Session handler assigned by session_set_save_handler().
515
 *
516
 * Cleans up stalled sessions.
517
 *
518
 * @param $lifetime
519
 *   The value of session.gc_maxlifetime, passed by PHP.
520
 *   Sessions not updated for more than $lifetime seconds will be removed.
521
 */
522
function _drupal_session_garbage_collection($lifetime) {
523
  // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
524
  // value. For example, if you want user sessions to stay in your database
525
  // for three weeks before deleting them, you need to set gc_maxlifetime
526
  // to '1814400'. At that value, only after a user doesn't log in after
527
  // three weeks (1814400 seconds) will his/her session be removed.
528
  db_delete('sessions')
529
    ->condition('timestamp', REQUEST_TIME - $lifetime, '<')
530
    ->execute();
531
  return TRUE;
532
}
533

    
534
/**
535
 * Determines whether to save session data of the current request.
536
 *
537
 * This function allows the caller to temporarily disable writing of
538
 * session data, should the request end while performing potentially
539
 * dangerous operations, such as manipulating the global $user object.
540
 * See http://drupal.org/node/218104 for usage.
541
 *
542
 * @param $status
543
 *   Disables writing of session data when FALSE, (re-)enables
544
 *   writing when TRUE.
545
 *
546
 * @return
547
 *   FALSE if writing session data has been disabled. Otherwise, TRUE.
548
 */
549
function drupal_save_session($status = NULL) {
550
  // PHP session ID, session, and cookie handling happens in the global scope.
551
  // This value has to persist across calls to drupal_static_reset(), since a
552
  // potentially wrong or disallowed session would be written otherwise.
553
  static $save_session = TRUE;
554
  if (isset($status)) {
555
    $save_session = $status;
556
  }
557
  return $save_session;
558
}