Index: update.php
===================================================================
RCS file: /cvs/drupal/drupal/update.php,v
retrieving revision 1.264
diff -u -9 -p -r1.264 update.php
--- update.php	24 Oct 2008 18:47:02 -0000	1.264
+++ update.php	26 Oct 2008 14:20:23 -0000
@@ -651,18 +651,19 @@ if (empty($op)) {
     drupal_maintenance_theme();
     print theme('update_page', '<form method="post" action="update.php?op=info"><input type="submit" value="Continue" /></form>', FALSE);
     exit;
   }
   install_goto('update.php?op=info');
 }
 
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 drupal_maintenance_theme();
+drupal_session_start();
 
 // This must happen *after* drupal_bootstrap(), since it calls
 // variable_(get|set), which only works after a full bootstrap.
 update_create_batch_table();
 
 // Turn error reporting back on. From now on, only fatal errors (which are
 // not passed through the error handler) will cause a message to be printed.
 ini_set('display_errors', TRUE);
 
Index: includes/batch.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/batch.inc,v
retrieving revision 1.26
diff -u -9 -p -r1.26 batch.inc
--- includes/batch.inc	22 Oct 2008 19:39:36 -0000	1.26
+++ includes/batch.inc	26 Oct 2008 14:20:23 -0000
@@ -347,18 +347,19 @@ function _batch_finished() {
     // Let drupal_redirect_form handle redirection logic.
     $form = isset($batch['form']) ? $batch['form'] : array();
     if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) {
       drupal_redirect_form($form, $redirect);
     }
 
     // We get here if $form['#redirect'] was FALSE, or if the form is a
     // multi-step form. We save the final $form_state value to be retrieved
     // by drupal_get_form, and we redirect to the originating page.
+    drupal_session_start();
     $_SESSION['batch_form_state'] = $_batch['form_state'];
     drupal_goto($_batch['source_page']);
   }
 }
 
 /**
  * Shutdown function: store the batch data for next request,
  * or clear the table if the batch is finished.
  */
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.242
diff -u -9 -p -r1.242 bootstrap.inc
--- includes/bootstrap.inc	25 Oct 2008 01:13:40 -0000	1.242
+++ includes/bootstrap.inc	26 Oct 2008 14:20:24 -0000
@@ -609,35 +609,42 @@ function variable_del($name) {
   cache_clear_all('variables', 'cache');
 
   unset($conf[$name]);
 }
 
 
 /**
  * Retrieve the current page from the cache.
  *
- * Note: we do not serve cached pages when status messages are waiting (from
- * a redirected form submission which was completed).
+ * Note: we do not serve cached pages to authenticated users, or to anonymous
+ * users when $_SESSION is non-empty (it may contain e.g. status messages from
+ * a form submission).
+ *
+ * @return
+ *   The cache object, if the page was found in the cache; TRUE if the page was
+ *   not found, but output buffering was started in order to possibly cache the
+ *   current request; FALSE if the page was not found, and the current request
+ *   may not be cached (e.g. because it belongs to an authenticated user).
  */
 function page_get_cache() {
   global $user, $base_root;
 
-  $cache = NULL;
-
-  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_set_message()) == 0) {
-    $cache = cache_get($base_root . request_uri(), 'cache_page');
-
-    if (empty($cache)) {
-      ob_start();
-    }
+  if ($user->uid || ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') || !empty($_SESSION)) {
+    return FALSE;
+  }
+  $cache = cache_get($base_root . request_uri(), 'cache_page');
+  if ($cache) {
+    return $cache;
+  }
+  else {
+    ob_start();
+    return TRUE;
   }
-
-  return $cache;
 }
 
 /**
  * Call all init or exit hooks without including all modules.
  *
  * @param $hook
  *   The name of the bootstrap hook we wish to invoke.
  */
 function bootstrap_invoke_all($hook) {
@@ -912,18 +939,20 @@ function watchdog($type, $message, $vari
  *   - 'status'
  *   - 'warning'
  *   - 'error'
  * @param $repeat
  *   If this is FALSE and the message is already set, then the message won't
  *   be repeated.
  */
 function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
   if ($message) {
+    drupal_session_start();
+
     if (!isset($_SESSION['messages'])) {
       $_SESSION['messages'] = array();
     }
 
     if (!isset($_SESSION['messages'][$type])) {
       $_SESSION['messages'][$type] = array();
     }
 
     if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
@@ -1050,19 +1079,19 @@ function drupal_bootstrap($phase = NULL)
  * drupal_bootstrap().
  *
  * @see drupal_bootstrap
  */
 function drupal_get_bootstrap_phase() {
   return drupal_bootstrap();
 }
 
 function _drupal_bootstrap($phase) {
-  global $conf;
+  global $conf, $user;
 
   switch ($phase) {
 
     case DRUPAL_BOOTSTRAP_CONFIGURATION:
       drupal_initialize_variables();
       // Start a page timer:
       timer_start('page');
       // Initialize the configuration
       conf_init();
@@ -1096,48 +1125,68 @@ function _drupal_bootstrap($phase) {
         header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
         print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.';
         exit();
       }
       break;
 
     case DRUPAL_BOOTSTRAP_SESSION:
       require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc');
       session_set_save_handler('_sess_open', '_sess_close', '_sess_read', '_sess_write', '_sess_destroy_sid', '_sess_gc');
-      session_start();
+      // If a session cookie is exists, initialize the session. Otherwise the
+      // session is only started on demand using drupal_session_start(), making
+      // anonymous sessions not use a session cookie unless something is stored
+      // in $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
+      if (isset($_COOKIE[session_name()])) {
+        drupal_session_start();
+        // Destroy empty anonymous sessions.
+        if (empty($_SESSION) && !$user->uid) {
+          session_destroy();
+        }
+      }
+      else {
+        $user = drupal_anonymous_user();
+      }
       break;
 
     case DRUPAL_BOOTSTRAP_VARIABLES:
       // Initialize configuration variables, using values from settings.php if available.
       $conf = variable_init(isset($conf) ? $conf : array());
       break;
 
     case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
       // Load module handling.
       require_once DRUPAL_ROOT . '/includes/module.inc';
       $cache_mode = variable_get('cache', CACHE_DISABLED);
       // Get the page from the cache.
-      $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();
+      $cache = $cache_mode == CACHE_DISABLED ? FALSE : page_get_cache();
       // If the skipping of the bootstrap hooks is not enforced, call hook_boot.
       if ($cache_mode != CACHE_AGGRESSIVE) {
         bootstrap_invoke_all('boot');
       }
       // If there is a cached page, display it.
-      if ($cache) {
+      if (is_object($cache)) {
         drupal_page_cache_header($cache);
         // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
         if ($cache_mode != CACHE_AGGRESSIVE) {
           bootstrap_invoke_all('exit');
         }
         // We are done.
         exit;
       }
       // Prepare for non-cached page workflow.
       drupal_page_header();
+      // If the session has not already been started and output buffering is
+      // not enabled, the session must be started now before the HTTP headers
+      // are sent. If output buffering is enabled, the session may be started
+      // at any time using drupal_session_start().
+      if ($cache === FALSE) {
+        drupal_session_start();
+      }
       break;
 
     case DRUPAL_BOOTSTRAP_LANGUAGE:
       drupal_init_language();
       break;
 
     case DRUPAL_BOOTSTRAP_PATH:
       require_once DRUPAL_ROOT . '/includes/path.inc';
       // Initialize $_GET['q'] prior to loading modules and invoking hook_init().
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.810
diff -u -9 -p -r1.810 common.inc
--- includes/common.inc	22 Oct 2008 19:39:36 -0000	1.810
+++ includes/common.inc	26 Oct 2008 14:20:24 -0000
@@ -2649,19 +2649,19 @@ function _drupal_bootstrap_full() {
  * The majority of all modern browsers support gzip or both of them.
  * We thus only deal with the gzip variant and unzip the cache in case
  * the browser does not accept gzip encoding.
  *
  * @see drupal_page_header
  */
 function page_set_cache() {
   global $user, $base_root;
 
-  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_get_messages(NULL, FALSE)) == 0) {
+  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && empty($_SESSION)) {
     // This will fail in some cases, see page_get_cache() for the explanation.
     if ($data = ob_get_contents()) {
       $cache = TRUE;
       if (variable_get('page_compression', TRUE) && function_exists('gzencode')) {
         // We do not store the data in case the zlib mode is deflate.
         // This should be rarely happening.
         if (zlib_get_coding_type() == 'deflate') {
           $cache = FALSE;
         }
Index: includes/session.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/session.inc,v
retrieving revision 1.61
diff -u -9 -p -r1.61 session.inc
--- includes/session.inc	11 Oct 2008 16:37:38 -0000	1.61
+++ includes/session.inc	26 Oct 2008 14:20:24 -0000
@@ -158,18 +158,31 @@ function _sess_write($key, $value) {
         db_query("UPDATE {users} SET access = %d WHERE uid = %d", REQUEST_TIME, $user->uid);
       }
     }
   }
 
   return TRUE;
 }
 
 /**
+ * Propagate $_SESSION and set session cookie if not already set. This function
+ * should be called explicitly before writing to $_SESSION.
+ */
+function drupal_session_start() {
+  static $called = FALSE;
+
+  if (!$called) {
+    $called = TRUE;
+    session_start();
+  }
+}
+
+/**
  * Called when an anonymous user becomes authenticated or vice-versa.
  */
 function drupal_session_regenerate() {
   $old_session_id = session_id();
   session_regenerate_id();
   db_query("UPDATE {sessions} SET sid = '%s' WHERE sid = '%s'", session_id(), $old_session_id);
 }
 
 /**
@@ -197,18 +210,21 @@ function drupal_session_count($timestamp
  * Session handler assigned by session_set_save_handler().
  *
  * Cleanup a specific session.
  *
  * @param string $sid
  *   Session ID.
  */
 function _sess_destroy_sid($sid) {
   db_query("DELETE FROM {sessions} WHERE sid = '%s'", $sid);
+  if ($sid == session_id()) {
+    setcookie(session_name(), '', time() - 36000, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), (bool)ini_get('session.cookie_secure'), (bool)ini_get('session.httponly'));
+  }
 }
 
 /**
  * End a specific user's session(s).
  *
  * @param string $uid
  *   User ID.
  */
 function drupal_session_destroy_uid($uid) {
