? 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 @@
 <?php
-// $Id: memcache.test,v 1.1.4.2 2009/12/05 00:28:13 Jeremy Exp $
+// $Id: cache.test,v 1.10 2009/09/13 17:49:51 dries Exp $
 
 class MemcacheTestCase extends DrupalWebTestCase {
   protected $default_bin = 'cache';
@@ -318,3 +318,225 @@ class MemCacheClearCase extends Memcache
                        t('All cache entries removed when the array exceeded the cache clear threshold.'));
   }
 }
+
+/**
+ * @file
+ * Provides SimpleTests for core session handling functionality.
+ */
+class MemcacheSessionTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => '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.'));
+    }
+  }
+}
