? memcache.module ? memcache.session.d7.patch ? sess.d7.patch Index: dmemcache.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/memcache/dmemcache.inc,v retrieving revision 1.1.4.1 diff -u -p -r1.1.4.1 dmemcache.inc --- dmemcache.inc 5 Dec 2009 00:26:30 -0000 1.1.4.1 +++ dmemcache.inc 12 Dec 2009 01:01:45 -0000 @@ -31,12 +31,14 @@ $_memcache_statistics = array('get' => a * @return bool */ function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) { + global $_memcache_statistics; $_memcache_statistics['set'][] = $key; $_memcache_statistics['bins'][] = $bin; if ($mc || ($mc = dmemcache_object($bin))) { $full_key = dmemcache_key($key, $bin); - if (!$mc->set($full_key, $value, MEMCACHE_COMPRESSED, $exp)) { + error_log("setting: ". $full_key . gmdate('c') . "\n", 3, '/tmp/log'); + if (!memcache_set($mc, $full_key, $value, MEMCACHE_COMPRESSED, $exp)) { return FALSE; } else { @@ -60,18 +62,18 @@ function dmemcache_get($key, $bin = 'cac $_memcache_statistics['bins'][] = $bin; if ($mc || ($mc = dmemcache_object($bin))) { $full_key = dmemcache_key($key, $bin); - $result = $mc->get($full_key); + $result = memcache_get($mc, $full_key); if ($result) { // We check $result->expire to see if the object has expired. If so, we // try and grab a lock. If we get the lock, we return FALSE instead of // the cached object which should cause it to be rebuilt. If we do not // get the lock, we return the cached object. The goal here is to avoid - // cache stampedes. + // cache stampedes. // By default the cache stampede semaphore is held for 15 seconds. This // can be adjusted by setting the memcache_stampede_semaphore variable. // TODO: Can we log when a sempahore expires versus being intentionally // freed to track when this is happening? - if (isset($result->expire) && $result->expire !== CACHE_PERMANENT && $result->expire <= time() && $mc->add($full_key .'_semaphore', '', FALSE, variable_get('memcache_stampede_semaphore', 15))) { + if (isset($result->expire) && $result->expire !== CACHE_PERMANENT && $result->expire <= time() && memcache_add($mc, $full_key .'_semaphore', '', FALSE, variable_get('memcache_stampede_semaphore', 15))) { $result = FALSE; } else { @@ -93,7 +95,7 @@ function dmemcache_get($key, $bin = 'cac function dmemcache_delete($key, $bin = 'cache', $mc = NULL) { if ($mc || ($mc = dmemcache_object($bin))) { $full_key = dmemcache_key($key, $bin); - return $mc->delete($full_key); + return memcache_delete($mc, $full_key); } return FALSE; } @@ -110,7 +112,7 @@ function dmemcache_delete($key, $bin = ' */ function dmemcache_flush($bin = 'cache', $mc = NULL) { if ($mc || ($mc = dmemcache_object($bin))) { - return $mc->flush(); + return memcache_flush($mc); } } @@ -126,13 +128,13 @@ function dmemcache_stats($bin = 'cache', if ($mc = dmemcache_object($bin)) { // The PHP Memcache extension 3.x version throws an error if the stats // type is NULL or not in {reset, malloc, slabs, cachedump, items, sizes}. - // If $type is 'default', then no parameter should be passed to the + // If $type is 'default', then no parameter should be passed to the // Memcache memcache_get_extended_stats() function. if ($type == 'default' || $type == '') { - return $mc->getExtendedStats(); + return memcache_getExtendedStats($mc); } else { - return $mc->getExtendedStats($type); + return memcache_getExtendedStats($mc, $type); } } } @@ -156,7 +158,7 @@ function dmemcache_object($bin = NULL, $ if ($flush) { foreach ($memcacheCache as $cluster) { - $cluster->close(); + memcache_close($cluster); } $memcacheCache = array(); } @@ -183,7 +185,7 @@ function dmemcache_object($bin = NULL, $ } else { // Create a new Memcache object. Each cluster gets its own Memcache object. - $memcache = new Memcache; +// $memcache = new Memcache; // A variable to track whether we've connected to the first server. $init = FALSE; @@ -195,7 +197,7 @@ function dmemcache_object($bin = NULL, $ // This is a server that belongs to this cluster. if (!$init) { // First server gets the connect. - if (@$memcache->connect($host, $port)) { + if ($memcache = @memcache_connect($host, $port)) { // Only set init to true if a connection was made. $init = TRUE; $memcache_online = TRUE; @@ -206,7 +208,7 @@ function dmemcache_object($bin = NULL, $ } else { // Subsequent servers gett addServer. - @$memcache->addServer($host, $port); + memcache_addServer($memcache, $host, $port); } } } Index: memcache-session.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/memcache/memcache-session.inc,v retrieving revision 1.4.2.1 diff -u -p -r1.4.2.1 memcache-session.inc --- memcache-session.inc 5 Dec 2009 00:26:30 -0000 1.4.2.1 +++ memcache-session.inc 12 Dec 2009 01:01:45 -0000 @@ -10,15 +10,15 @@ require_once dirname(__FILE__) . '/dmemc * An alternative to includes/session.inc. */ -function sess_open($save_path, $session_name) { +function _drupal_session_open() { return TRUE; } -function sess_close() { +function _drupal_session_close() { return TRUE; } -function sess_read($key) { +function _drupal_session_read($key) { global $user; // Write and Close handlers are called after destructing objects since PHP 5.0.5 @@ -26,20 +26,20 @@ function sess_read($key) { // So we are moving session closure before destructing objects. register_shutdown_function('session_write_close'); - // Handle the case of first time visitors and clients that don't store + // Handle the case of first time visitors and clients that don't store // cookies (eg. web crawlers). if (!isset($_COOKIE[session_name()])) { $user = drupal_anonymous_user(); return ''; } - // Otherwise, if the session is still active, we have a record of the + // Otherwise, if the session is still active, we have a record of the // client's session in memcache. $session = dmemcache_get($key, 'session'); - - $user = sess_user_load($session); - // Record whether this session contains data so that in sess_write() it can + $user = _memcache_session_user_load($session); + + // Record whether this session contains data so that in sess_write() it can // be determined whether to skip a write. $user->session_data_present_at_load = !empty($session->session); @@ -54,14 +54,14 @@ function sess_read($key) { * 1a. Without session data. * 1b. With session data. * 1c. Session saving has been turned off programatically - * (see session_save_session()). + * (see drupal_save_session()). * 1d. Without session data but had session data at the beginning of the request * (thus a write must be made to clear stored session data). * 2. Authenticated user. * 2a. Without session data. * 2b. With session data. * 2c. Session saving has been turned off programatically - * (see session_save_session()). + * (see drupal_save_session()). * * @param $key * The session ID. @@ -70,13 +70,10 @@ function sess_read($key) { * @return * TRUE. */ -function sess_write($key, $value) { +function _drupal_session_write($key, $value) { global $user; - // If the client doesn't have a session, and one isn't being created ($value), - // do nothing. If session saving has been turned off, do nothing. - // Cases 1a, 1c, and 2c are covered here. - if ((empty($_COOKIE[session_name()]) && empty($value)) || !session_save_session()) { + if (!drupal_save_session()) { return TRUE; } @@ -90,15 +87,17 @@ function sess_write($key, $value) { $session->timestamp = time(); // If this is an authenticated user, or there is something to save in the - // session, or this is an anonymous user who currently has nothing in the + // session, or this is an anonymous user who currently has nothing in the // session but did have something in session storage, write it to memcache. // If $user->session_data_present_at_load is not set, the current user - // was created during this request and it's safest to do a write. + // was created during this request and it's safest to do a write. // Cases 1b, 1d, 2a, and 2b are covered here. if ($user->uid || !empty($value) || empty($value) && (!isset($user->session_data_present_at_load) || $user->session_data_present_at_load)) { dmemcache_set($key, $session, ini_get('session.gc_maxlifetime'), 'session'); if ($user->uid && $session->timestamp - $user->access > variable_get('session_write_interval', 360)) { - db_query('UPDATE {users} SET access = %d WHERE uid = %d', $session->timestamp, $user->uid); + db_update('users') + ->fields(array('access' => $user->uid)) + ->condition(array('uid' => $user->uid)); // Update the user access time so that the dmemcache_set() call // caches the updated time. $user->access = $session->timestamp; @@ -109,65 +108,175 @@ function sess_write($key, $value) { unset($user->session_data_present_at_load); // Store the session id so we can locate the session with the user id. $user->sid = $key; - + dmemcache_set($user->uid, $user, ini_get('session.gc_maxlifetime'), 'users'); } return TRUE; } -function sess_regenerate() { - // We code around http://bugs.php.net/bug.php?id=32802 by destroying - // the session cookie by setting expiration in the past (a negative - // value). This issue only arises in PHP versions before 4.4.0, - // regardless of the Drupal configuration. - // TODO: remove this when we require at least PHP 4.4.0 - if (isset($_COOKIE[session_name()])) { - setcookie(session_name(), '', time() - 42000, '/'); - } - // Store the current (anonymous) session id. - $old_session_id = session_id(); +/** + * Called by PHP session handling with the PHP session ID to end a user's session. + * + * @param string $sid + * the session id + */ +function _drupal_session_destroy($sid) { + dmemcache_delete($sid, 'session'); + // Reset $_SESSION and $user to prevent a new session from being started + // in drupal_session_commit(). + $_SESSION = array(); + $user = drupal_anonymous_user(); - // Generate the new (authenticated) session id. - session_regenerate_id(); - $key = session_id(); + // Unset the session cookies. + _drupal_session_delete_cookie(session_name()); +} - // Grab the user's information that is cached with the anonymous key. - $info = dmemcache_get($old_session_id, 'session'); +function _drupal_session_garbage_collection($lifetime) { + // Automatic with memcached. + // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough + // value. For example, if you want user sessions to stay in your database + // for three weeks before deleting them, you need to set gc_maxlifetime + // to '1814400'. At that value, only after a user doesn't log in after + // three weeks (1814400 seconds) will his/her session be removed. + return TRUE; +} - // Update it. - $info->sid = $key; +/** + * Initialize the session handler, starting a session if needed. + */ +function drupal_session_initialize() { + global $user; - // Store it with the new key. - dmemcache_set($key, $info, ini_get('session.gc_maxlifetime'), 'session'); + session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection'); - // Clear the old data from the cache. - dmemcache_delete($old_session_id, 'session'); + if (isset($_COOKIE[session_name()])) { + // If a session cookie exists, initialize the session. Otherwise the + // session is only started on demand in drupal_session_commit(), making + // anonymous users not use a session cookie unless something is stored in + // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. + drupal_session_start(); + if (!empty($user->uid) || !empty($_SESSION)) { + drupal_page_is_cacheable(FALSE); + } + } + else { + // Set a session identifier for this request. This is necessary because + // we lazyly start sessions at the end of this request, and some + // processes (like drupal_get_token()) needs to know the future + // session ID in advance. + $user = drupal_anonymous_user(); + session_id(md5(uniqid('', TRUE))); + } } + /** * Counts how many users have sessions. Can count either anonymous sessions, authenticated sessions, or both. * Would be insane slow with memcached as we would need to retrieve at least the stats of all object. * Not implemented. */ -function sess_count($timestamp = 0, $anonymous = true) { +function drupal_session_count($timestamp = 0, $anonymous = true) { } /** - * Called by PHP session handling with the PHP session ID to end a user's session. + * Forcefully start a session, preserving already set session data. * - * @param string $sid - * the session id + * @ingroup php_wrappers */ -function sess_destroy_sid($sid) { - dmemcache_delete($sid, 'session'); +function drupal_session_start() { + if (!drupal_session_started()) { + // Save current session data before starting it, as PHP will destroy it. + $session_data = isset($_SESSION) ? $_SESSION : NULL; + + session_start(); + drupal_session_started(TRUE); + + // Restore session data. + if (!empty($session_data)) { + $_SESSION += $session_data; + } + } } /** + * Commit the current session, if necessary. + * + * If an anonymous user already have an empty session, destroy it. + */ +function drupal_session_commit() { + global $user; + + if (!drupal_save_session()) { + // We don't have anything to do if we are not allowed to save the session. + return; + } + + if (empty($user->uid) && empty($_SESSION)) { + // There is no session data to store, destroy the session if it was + // previously started. + if (drupal_session_started()) { + session_destroy(); + } + } + else { + // There is session data to store. Start the session if it is not already + // started. + if (!drupal_session_started()) { + drupal_session_start(); + } + // Write the session data. + session_write_close(); + } +} + +/** + * Return whether a session has been started. + */ +function drupal_session_started($set = NULL) { + static $session_started = FALSE; + if (isset($set)) { + $session_started = $set; + } + return $session_started && session_id(); +} + +/** + * Called when an anonymous user becomes authenticated or vice-versa. + * + * @ingroup php_wrappers + */ +function drupal_session_regenerate() { + global $user; + if (drupal_session_started()) { + $old_session_id = session_id(); + session_regenerate_id(); + $new_session_id = session_id(); + } + else { + // Start the session when it doesn't exist yet. + // Preserve the logged in user, as it will be reset to anonymous + // by _drupal_session_read. + $account = $user; + drupal_session_start(); + $user = $account; + } + + if (isset($old_session_id)) { + $info = dmemcache_get($old_session_id, 'session'); + $info->sid = $new_session_id; + dmemcache_set($new_session_id, $info, ini_get('session.gc_maxlifetime'), 'session'); + // Clear the old data from the cache. + dmemcache_delete($old_session_id, 'session'); + } +} + + +/** * End a specific user's session. */ -function sess_destroy_uid($uid) { +function drupal_session_destroy_uid($uid) { $user = dmemcache_get($uid, 'users'); if (is_object($user) && isset($user->sid)) { dmemcache_delete($user->sid, 'session'); @@ -175,15 +284,6 @@ function sess_destroy_uid($uid) { dmemcache_delete($uid, 'users'); } -function sess_gc($lifetime) { - // Automatic with memcached. - // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough - // value. For example, if you want user sessions to stay in your database - // for three weeks before deleting them, you need to set gc_maxlifetime - // to '1814400'. At that value, only after a user doesn't log in after - // three weeks (1814400 seconds) will his/her session be removed. - return TRUE; -} /** * Determine whether to save session data of the current request. @@ -197,8 +297,8 @@ function sess_gc($lifetime) { * @return * FALSE if writing session data has been disabled. Otherwise, TRUE. */ -function session_save_session($status = NULL) { - static $save_session = TRUE; +function drupal_save_session($status = NULL) { + $save_session = &drupal_static(__FUNCTION__, TRUE); if (isset($status)) { $save_session = $status; } @@ -213,7 +313,7 @@ function session_save_session($status = * @return $user * The user object. */ -function sess_user_load($session) { +function _memcache_session_user_load($session) { // We found the client's session record and they are an authenticated user. if ($session && $session->uid != 0) { $user = dmemcache_get($session->uid, 'users'); @@ -221,8 +321,8 @@ function sess_user_load($session) { // If the cached user was not found in the 'users' memcache bin, $user will // be FALSE. // In either of these cases, the user must be retrieved from the database. - if (!$user->uid && isset($session->uid) && $session->uid != 0) { - $user = db_fetch_object(db_query('SELECT u.* FROM {users} u WHERE u.uid = %d', $session->uid)); + if (!$user && isset($session->uid) && $session->uid != 0) { + $user = db_query('SELECT u.* FROM {users} u WHERE u.uid = :uid', array('uid' => $session->uid))->fetchObject(); $user = drupal_unpack($user); // Normally we would join the session and user tables. But we already @@ -236,8 +336,8 @@ function sess_user_load($session) { // Add roles element to $user $user->roles = array(); $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - $result = db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d", $user->uid); - while ($role = db_fetch_object($result)) { + $result = 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)); + foreach ($result as $role) { $user->roles[$role->rid] = $role->name; } } @@ -266,3 +366,20 @@ function sess_user_load($session) { return $user; } + +/** + * Deletes the session cookie. + * + * @param $name + * Name of session cookie to delete. + * @param $force_insecure + * Fornce cookie to be insecure. + */ +function _drupal_session_delete_cookie($name, $force_insecure = FALSE) { + if (isset($_COOKIE[$name])) { + $params = session_get_cookie_params(); + setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']); + unset($_COOKIE[$name]); + } +} + Index: memcache.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/memcache/memcache.inc,v retrieving revision 1.28.2.1 diff -u -p -r1.28.2.1 memcache.inc --- memcache.inc 5 Dec 2009 00:26:30 -0000 1.28.2.1 +++ memcache.inc 12 Dec 2009 01:01:45 -0000 @@ -46,7 +46,7 @@ class MemCacheDrupal implements DrupalCa } function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL) { - $created = time(); + $created = time(); // Create new cache object. $cache = new stdClass; @@ -54,6 +54,12 @@ class MemCacheDrupal implements DrupalCa $cache->data = is_object($data) ? clone $data : $data; $cache->created = $created; $cache->headers = $headers; + + if ($this->bin == 'cache_ssi') { + $cache->data['#real_value'] = $cache->data['#markup']; + $cache->data['#markup'] = ssi_include($cid); + } + if ($expire == CACHE_TEMPORARY) { // Convert CACHE_TEMPORARY (-1) into something that will live in memcache // until the next flush. @@ -120,7 +126,7 @@ class MemCacheDrupal implements DrupalCa } } } - + function isEmpty() { // We do not know so err on the safe side? return FALSE; Index: memcache.info =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/memcache/memcache.info,v retrieving revision 1.2.2.1 diff -u -p -r1.2.2.1 memcache.info --- memcache.info 5 Dec 2009 00:26:30 -0000 1.2.2.1 +++ memcache.info 12 Dec 2009 01:01:45 -0000 @@ -2,3 +2,7 @@ name = Memcache description = High performance integration with memcache. package = Caching +core = 7.x +files[] = memcache.inc +files[] = memcache.module +files[] = memcache.test Index: memcache.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/memcache/Attic/memcache.test,v retrieving revision 1.1.4.2 diff -u -p -r1.1.4.2 memcache.test --- memcache.test 5 Dec 2009 00:28:13 -0000 1.1.4.2 +++ memcache.test 12 Dec 2009 01:01:45 -0000 @@ -1,5 +1,5 @@ 'Session tests', + 'description' => 'Memcache session handling tests.', + 'group' => 'Memcache' + ); + } + + function setUp() { + parent::setUp('session_test'); + } + + /** + * Tests for drupal_save_session() and drupal_session_regenerate(). + */ + function testSessionSaveRegenerate() { + $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.'), t('Session')); + $this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session')); + $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session')); + $this->assertTrue(drupal_save_session(TRUE), t('drupal_save_session() correctly returns TRUE when called with TRUE.'), t('Session')); + $this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when saving has been enabled.'), t('Session')); + + // Test session hardening code from SA-2008-044. + $user = $this->drupalCreateUser(array('access content')); + + // Enable sessions. + $this->sessionReset($user->uid); + + // Make sure the session cookie is set as HttpOnly. + $this->drupalLogin($user); + $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), t('Session cookie is set as HttpOnly.')); + $this->drupalLogout(); + + // Verify that the session is regenerated if a module calls exit + // in hook_user_login(). + user_save($user, array('name' => 'session_test_user')); + $user->name = 'session_test_user'; + $this->drupalGet('session-test/id'); + $matches = array(); + preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); + $this->assertTrue(!empty($matches[1]) , t('Found session ID before logging in.')); + $original_session = $matches[1]; + + // We cannot use $this->drupalLogin($user); because we exit in + // session_test_user_login() which breaks a normal assertion. + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw + ); + $this->drupalPost('user', $edit, t('Log in')); + $this->drupalGet('user'); + $pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login')); + $this->_logged_in = $pass; + + $this->drupalGet('session-test/id'); + $matches = array(); + preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); + $this->assertTrue(!empty($matches[1]) , t('Found session ID after logging in.')); + $this->assertTrue($matches[1] != $original_session, t('Session ID changed after login.')); + } + + /** + * Test data persistence via the session_test module callbacks. Also tests + * drupal_session_count() since session data is already generated here. + */ + function testDataPersistence() { + $user = $this->drupalCreateUser(array('access content')); + // Enable sessions. + $this->sessionReset($user->uid); + + $this->drupalLogin($user); + + $value_1 = $this->randomName(); + $this->drupalGet('session-test/set/' . $value_1); + $this->assertText($value_1, t('The session value was stored.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_1, t('Session correctly returned the stored data for an authenticated user.'), t('Session')); + + // Attempt to write over val_1. If drupal_save_session(FALSE) is working. + // properly, val_1 will still be set. + $value_2 = $this->randomName(); + $this->drupalGet('session-test/no-set/' . $value_2); + $this->assertText($value_2, t('The session value was correctly passed to session-test/no-set.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_1, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session')); + + // Switch browser cookie to anonymous user, then back to user 1. + $this->sessionReset(); + $this->sessionReset($user->uid); + $this->assertText($value_1, t('Session data persists through browser close.'), t('Session')); + + // Logout the user and make sure the stored value no longer persists. + $this->drupalLogout(); + + $this->sessionReset(); + $this->drupalGet('session-test/get'); + $this->assertNoText($value_1, t("After logout, previous user's session data is not available."), t('Session')); + + // Now try to store some data as an anonymous user. + $value_3 = $this->randomName(); + $this->drupalGet('session-test/set/' . $value_3); + $this->assertText($value_3, t('Session data stored for anonymous user.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_3, t('Session correctly returned the stored data for an anonymous user.'), t('Session')); + + // Try to store data when drupal_save_session(FALSE). + $value_4 = $this->randomName(); + $this->drupalGet('session-test/no-set/' . $value_4); + $this->assertText($value_4, t('The session value was correctly passed to session-test/no-set.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_3, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session')); + + // Login, the data should persist. + $this->drupalLogin($user); + $this->sessionReset($user->uid); + $this->drupalGet('session-test/get'); + $this->assertNoText($value_1, t('Session has persisted for an authenticated user after logging out and then back in.'), t('Session')); + + // Change session and create another user. + $user2 = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user2->uid); + $this->drupalLogin($user2); + } + + /** + * Test that empty anonymous sessions are destroyed. + */ + function testEmptyAnonymousSession() { + // Verify that no session is automatically created for anonymous user. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + + // The same behavior is expected when caching is enabled. + variable_set('cache', CACHE_NORMAL); + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.')); + + // Start a new session by setting a message. + $this->drupalGet('session-test/set-message'); + $this->assertSessionCookie(TRUE); + $this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.')); + + // Display the message, during the same request the session is destroyed + // and the session cookie is unset. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(FALSE); + $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.')); + $this->assertText(t('This is a dummy message.'), t('Message was displayed.')); + $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.')); + + // Verify that session was destroyed. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + $this->assertNoText(t('This is a dummy message.'), t('Message was not cached.')); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); + $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.')); + + // Verify that no session is created if drupal_save_session(FALSE) is called. + $this->drupalGet('session-test/set-message-but-dont-save'); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + + // Verify that no message is displayed. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + $this->assertNoText(t('This is a dummy message.'), t('The message was not saved.')); + } + + /** + * Reset the cookie file so that it refers to the specified user. + * + * @param $uid User id to set as the active session. + */ + function sessionReset($uid = 0) { + // Close the internal browser. + $this->curlClose(); + $this->loggedInUser = FALSE; + + // Change cookie file for user. + $this->cookieFile = file_directory_path('temporary') . '/cookie.' . $uid . '.txt'; + $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile; + $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE; + $this->drupalGet('session-test/get'); + $this->assertResponse(200, t('Session test module is correctly enabled.'), t('Session')); + } + + /** + * Assert whether the SimpleTest browser sent a session cookie. + */ + function assertSessionCookie($sent) { + if ($sent) { + $this->assertNotNull($this->session_id, t('Session cookie was sent.')); + } + else { + $this->assertNull($this->session_id, t('Session cookie was not sent.')); + } + } + + /** + * Assert whether $_SESSION is empty at the beginning of the request. + */ + function assertSessionEmpty($empty) { + if ($empty) { + $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', t('Session was empty.')); + } + else { + $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', t('Session was not empty.')); + } + } +}