session.inc 20 KB

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