cms/drupal/includes/session.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     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   }
       
   375   session_id(drupal_random_key());
       
   376 
       
   377   if (isset($old_session_id)) {
       
   378     $params = session_get_cookie_params();
       
   379     $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
       
   380     setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
       
   381     $fields = array('sid' => session_id());
       
   382     if ($is_https) {
       
   383       $fields['ssid'] = session_id();
       
   384       // If the "secure pages" setting is enabled, use the newly-created
       
   385       // insecure session identifier as the regenerated sid.
       
   386       if (variable_get('https', FALSE)) {
       
   387         $fields['sid'] = $session_id;
       
   388       }
       
   389     }
       
   390     db_update('sessions')
       
   391       ->fields($fields)
       
   392       ->condition($is_https ? 'ssid' : 'sid', $old_session_id)
       
   393       ->execute();
       
   394   }
       
   395   elseif (isset($old_insecure_session_id)) {
       
   396     // If logging in to the secure site, and there was no active session on the
       
   397     // secure site but a session was active on the insecure site, update the
       
   398     // insecure session with the new session identifiers.
       
   399     db_update('sessions')
       
   400       ->fields(array('sid' => $session_id, 'ssid' => session_id()))
       
   401       ->condition('sid', $old_insecure_session_id)
       
   402       ->execute();
       
   403   }
       
   404   else {
       
   405     // Start the session when it doesn't exist yet.
       
   406     // Preserve the logged in user, as it will be reset to anonymous
       
   407     // by _drupal_session_read.
       
   408     $account = $user;
       
   409     drupal_session_start();
       
   410     $user = $account;
       
   411   }
       
   412   date_default_timezone_set(drupal_get_user_timezone());
       
   413 }
       
   414 
       
   415 /**
       
   416  * Session handler assigned by session_set_save_handler().
       
   417  *
       
   418  * Cleans up a specific session.
       
   419  *
       
   420  * @param $sid
       
   421  *   Session ID.
       
   422  */
       
   423 function _drupal_session_destroy($sid) {
       
   424   global $user, $is_https;
       
   425 
       
   426   // Nothing to do if we are not allowed to change the session.
       
   427   if (!drupal_save_session()) {
       
   428     return TRUE;
       
   429   }
       
   430 
       
   431   // Delete session data.
       
   432   db_delete('sessions')
       
   433     ->condition($is_https ? 'ssid' : 'sid', $sid)
       
   434     ->execute();
       
   435 
       
   436   // Reset $_SESSION and $user to prevent a new session from being started
       
   437   // in drupal_session_commit().
       
   438   $_SESSION = array();
       
   439   $user = drupal_anonymous_user();
       
   440 
       
   441   // Unset the session cookies.
       
   442   _drupal_session_delete_cookie(session_name());
       
   443   if ($is_https) {
       
   444     _drupal_session_delete_cookie(substr(session_name(), 1), FALSE);
       
   445   }
       
   446   elseif (variable_get('https', FALSE)) {
       
   447     _drupal_session_delete_cookie('S' . session_name(), TRUE);
       
   448   }
       
   449 
       
   450   return TRUE;
       
   451 }
       
   452 
       
   453 /**
       
   454  * Deletes the session cookie.
       
   455  *
       
   456  * @param $name
       
   457  *   Name of session cookie to delete.
       
   458  * @param boolean $secure
       
   459  *   Force the secure value of the cookie.
       
   460  */
       
   461 function _drupal_session_delete_cookie($name, $secure = NULL) {
       
   462   global $is_https;
       
   463   if (isset($_COOKIE[$name]) || (!$is_https && $secure === TRUE)) {
       
   464     $params = session_get_cookie_params();
       
   465     if ($secure !== NULL) {
       
   466       $params['secure'] = $secure;
       
   467     }
       
   468     setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
       
   469     unset($_COOKIE[$name]);
       
   470   }
       
   471 }
       
   472 
       
   473 /**
       
   474  * Ends a specific user's session(s).
       
   475  *
       
   476  * @param $uid
       
   477  *   User ID.
       
   478  */
       
   479 function drupal_session_destroy_uid($uid) {
       
   480   // Nothing to do if we are not allowed to change the session.
       
   481   if (!drupal_save_session()) {
       
   482     return;
       
   483   }
       
   484 
       
   485   db_delete('sessions')
       
   486     ->condition('uid', $uid)
       
   487     ->execute();
       
   488 }
       
   489 
       
   490 /**
       
   491  * Session handler assigned by session_set_save_handler().
       
   492  *
       
   493  * Cleans up stalled sessions.
       
   494  *
       
   495  * @param $lifetime
       
   496  *   The value of session.gc_maxlifetime, passed by PHP.
       
   497  *   Sessions not updated for more than $lifetime seconds will be removed.
       
   498  */
       
   499 function _drupal_session_garbage_collection($lifetime) {
       
   500   // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
       
   501   // value. For example, if you want user sessions to stay in your database
       
   502   // for three weeks before deleting them, you need to set gc_maxlifetime
       
   503   // to '1814400'. At that value, only after a user doesn't log in after
       
   504   // three weeks (1814400 seconds) will his/her session be removed.
       
   505   db_delete('sessions')
       
   506     ->condition('timestamp', REQUEST_TIME - $lifetime, '<')
       
   507     ->execute();
       
   508   return TRUE;
       
   509 }
       
   510 
       
   511 /**
       
   512  * Determines whether to save session data of the current request.
       
   513  *
       
   514  * This function allows the caller to temporarily disable writing of
       
   515  * session data, should the request end while performing potentially
       
   516  * dangerous operations, such as manipulating the global $user object.
       
   517  * See http://drupal.org/node/218104 for usage.
       
   518  *
       
   519  * @param $status
       
   520  *   Disables writing of session data when FALSE, (re-)enables
       
   521  *   writing when TRUE.
       
   522  *
       
   523  * @return
       
   524  *   FALSE if writing session data has been disabled. Otherwise, TRUE.
       
   525  */
       
   526 function drupal_save_session($status = NULL) {
       
   527   // PHP session ID, session, and cookie handling happens in the global scope.
       
   528   // This value has to persist across calls to drupal_static_reset(), since a
       
   529   // potentially wrong or disallowed session would be written otherwise.
       
   530   static $save_session = TRUE;
       
   531   if (isset($status)) {
       
   532     $save_session = $status;
       
   533   }
       
   534   return $save_session;
       
   535 }