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 28 Oct 2008 17:28:52 -0000 @@ -651,18 +651,19 @@ if (empty($op)) { drupal_maintenance_theme(); print theme('update_page', '
', 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 28 Oct 2008 17:28:52 -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 28 Oct 2008 17:28:52 -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 +919,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 +1059,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 +1105,64 @@ 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 users 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(); + } + 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.811 diff -u -9 -p -r1.811 common.inc --- includes/common.inc 26 Oct 2008 18:06:38 -0000 1.811 +++ includes/common.inc 28 Oct 2008 17:28:52 -0000 @@ -289,36 +289,45 @@ function drupal_get_destination() { * - 303 See Other * - 304 Not Modified * - 305 Use Proxy * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance") * Note: Other values are defined by RFC 2616, but are rarely used and poorly * supported. * @see drupal_get_destination() */ function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { + global $user; if (isset($_REQUEST['destination'])) { extract(parse_url(urldecode($_REQUEST['destination']))); } $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE)); // Remove newlines from the URL to avoid header injection attacks. $url = str_replace(array("\n", "\r"), '', $url); // Allow modules to react to the end of the page request before redirecting. // We do not want this while running update.php. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('exit', $url); } - // Even though session_write_close() is registered as a shutdown function, we - // need all session data written to the database before redirecting. - session_write_close(); + if (drupal_session_start(FALSE)) { + if (empty($_SESSION) && !$user->uid) { + // Destroy empty anonymous sessions if possible. + session_destroy(); + } else { + // Even though session_write_close() is registered as a shutdown + // function, we need all session data written to the database before + // redirecting. + session_write_close(); + } + } header('Location: ' . $url, TRUE, $http_response_code); // The "Location" header sends a redirect status code to the HTTP daemon. In // some cases this can be wrong, so we make sure none of the code below the // drupal_goto() call gets executed upon redirection. exit(); } @@ -1585,18 +1594,24 @@ function l($text, $path, array $options } /** * Perform end-of-request tasks. * * This function sets the page cache if appropriate, and allows modules to * react to the closing of the page by calling hook_exit(). */ function drupal_page_footer() { + global $user; + + // Destroy empty anonymous sessions if possible. + if (!headers_sent() && drupal_session_start(FALSE) && empty($_SESSION) && !$user->uid) { + session_destroy(); + } if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { page_set_cache(); } module_invoke_all('exit'); registry_cache_hook_implementations(FALSE, TRUE); registry_cache_path_files(); @@ -2678,19 +2693,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 28 Oct 2008 17:28:53 -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($start = TRUE) { + static $started = FALSE; + if ($start && !$started) { + $started = TRUE; + session_start(); + } + return $started; +} + +/** * 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,19 @@ 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); + setcookie(session_name(), '', time() - 3600, 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) {