=== modified file '.htaccess' --- .htaccess 2008-12-11 04:56:37 +0000 +++ .htaccess 2009-05-11 06:03:56 +0000 @@ -66,8 +66,10 @@ # Cache all files for 2 weeks after access (A). ExpiresDefault A1209600 - # Do not cache dynamically generated pages. - ExpiresByType text/html A1 + + # Caching headers for dynamically generated pages are set from PHP. + ExpiresActive Off + # Various rewrite rules. === modified file 'includes/batch.inc' --- includes/batch.inc 2008-12-06 09:02:33 +0000 +++ includes/batch.inc 2009-05-11 06:03:56 +0000 @@ -338,7 +338,7 @@ // 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. - $_SESSION['batch_form_state'] = $_batch['form_state']; + drupal_set_session('batch_form_state', $_batch['form_state']); drupal_goto($_batch['source_page']); } } === modified file 'includes/bootstrap.inc' --- includes/bootstrap.inc 2009-04-30 00:36:53 +0000 +++ includes/bootstrap.inc 2009-05-13 19:52:02 +0000 @@ -1,5 +1,5 @@ uid || ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') || count(drupal_get_messages(NULL, FALSE)) || $_SERVER['SERVER_SOFTWARE'] === 'PHP CLI') { + return FALSE; } - $cache = NULL; - - if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && count(drupal_set_message()) == 0 && $_SERVER['SERVER_SOFTWARE'] !== 'PHP CLI') { + if ($retrieve) { $cache = cache_get($base_root . request_uri(), 'cache_page'); - - if (empty($cache)) { + if ($cache) { + return $cache; + } + else { ob_start(); - $status = TRUE; + $ob_started = TRUE; } } - - return $cache; + return $ob_started; } /** @@ -618,73 +627,260 @@ } /** + * Set an HTTP response header for the current page. + * + * Note: When sending a Content-Type header, always include a 'charset' type, + * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). + * + * @param $name + * The HTTP header name, or a status code followed by a reason phrase, e.g. + * "404 Not Found". + * @param $value + * The HTTP header value; if omitted, the specified header is unset. + * @param $append + * Whether to append the value to an existing header or to replace it. + */ +function drupal_set_header($name = NULL, $value = NULL, $append = FALSE) { + // The headers as name/value pairs. + static $headers = array(); + + if (!isset($name)) { + return $headers; + } + + // Save status codes using the special key ":status". + if (preg_match('/^\d{3} /', $name)) { + $value = $name; + $name = $name_lower = ':status'; + } + else { + $name_lower = strtolower($name); + } + _drupal_set_preferred_header_name($name); + + if (!isset($value)) { + $headers[$name_lower] = FALSE; + } + elseif (isset($headers[$name_lower]) && $append) { + // Multiple headers with identical names may be combined using comma (RFC + // 2616, section 4.2). + $headers[$name_lower] .= ',' . $value; + } + else { + $headers[$name_lower] = $value; + } + drupal_send_headers(array($name => $headers[$name_lower]), TRUE); +} + +/** + * Get the HTTP response headers for the current page. + * + * @param $name + * An HTTP header name. If omitted, all headers are returned as name/value + * pairs. If an array value is FALSE, the header has been unset. + * @return + * A string containing the header value, or FALSE if the header has been set, + * or NULL if the header has not been set. + */ +function drupal_get_header($name = NULL) { + $headers = drupal_set_header(); + if (isset($name)) { + $name = strtolower($name); + return isset($headers[$name]) ? $headers[$name] : NULL; + } + else { + return $headers; + } +} + +/** + * Header names are case-insensitive, but for maximum compatibility they should + * follow "common form" (see RFC 2617, section 4.2). + */ +function _drupal_set_preferred_header_name($name = NULL) { + static $header_names = array(); + + if (!isset($name)) { + return $header_names; + } + $header_names[strtolower($name)] = $name; +} + +/** + * Send the HTTP response headers previously set using drupal_set_header(). + * Add default headers, unless they have been replaced or unset using + * drupal_set_header(). + * + * @param $default_headers + * An array of headers as name/value pairs. + * @param $single + * If TRUE and headers have already be sent, send only the specified header. + */ +function drupal_send_headers($default_headers = array(), $only_default = FALSE) { + static $headers_sent = FALSE; + $headers = drupal_get_header(); + if ($only_default && $headers_sent) { + $headers = array(); + } + $headers_sent = TRUE; + + $header_names = _drupal_set_preferred_header_name(); + foreach ($default_headers as $name => $value) { + $name_lower = strtolower($name); + if (!isset($headers[$name_lower])) { + $headers[$name_lower] = $value; + $header_names[$name_lower] = $name; + } + } + foreach ($headers as $name_lower => $value) { + if ($name_lower == ':status') { + header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value); + } + // Skip headers that have been unset. + elseif ($value) { + header($header_names[$name_lower] . ': ' . $value); + } + } +} + +/** * Set HTTP headers in preparation for a page response. * - * Authenticated users are always given a 'no-cache' header, and will - * fetch a fresh page on every request. This prevents authenticated - * users seeing locally cached pages that show them as logged out. + * Authenticated users are always given a 'no-cache' header, and will fetch a + * fresh page on every request. This prevents authenticated users from seeing + * locally cached pages. + * + * Also give each page a unique ETag. This will force clients to include both + * an If-Modified-Since header and an If-None-Match header when doing + * conditional requests for the page (required by RFC 2616, section 13.3.4), + * making the validation more robust. This is a workaround for a bug in Mozilla + * Firefox that is triggered when Drupal's caching is enabled and the user + * accesses Drupal via an HTTP proxy (see + * https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an authenticated + * user requests a page, and then logs out and requests the same page again, + * Firefox may send a conditional request based on the page that was cached + * locally when the user was logged in. If this page did not have an ETag + * header, the request only contains an If-Modified-Since header. The date will + * be recent, because with authenticated users the Last-Modified header always + * refers to the time of the request. If the user accesses Drupal via a proxy + * server, and the proxy already has a cached copy of the anonymous page with an + * older Last-Modified date, the proxy may respond with 304 Not Modified, making + * the client think that the anonymous and authenticated pageviews are + * identical. * * @see page_set_cache() */ function drupal_page_header() { - header("Expires: Sun, 19 Nov 1978 05:00:00 GMT"); - header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT"); - header("Cache-Control: store, no-cache, must-revalidate"); - header("Cache-Control: post-check=0, pre-check=0", FALSE); + static $headers_sent = FALSE; + if ($headers_sent) { + return TRUE; + } + $headers_sent = TRUE; + + $default_headers = array( + 'Expires' => 'Sun, 11 Mar 1984 12:00:00 GMT', + 'Last-Modified' => gmdate(DATE_RFC1123, $_SERVER['REQUEST_TIME']), + 'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0', + 'ETag' => '"' . $_SERVER['REQUEST_TIME'] . '"', + ); + drupal_send_headers($default_headers); } /** * Set HTTP headers in preparation for a cached page response. * - * The general approach here is that anonymous users can keep a local - * cache of the page, but must revalidate it on every request. Then, - * they are given a '304 Not Modified' response as long as they stay - * logged out and the page has not been modified. + * The headers allow as much as possible in proxies and browsers without any + * particular knowledge about the pages. Modules can override these headers + * using drupal_set_header(). * + * If the request is conditional (using If-Modified-Since and If-None-Match), + * and the conditions match those currently in the cache, a 304 Not Modified + * response is sent. */ -function drupal_page_cache_header($cache) { - // Set default values: - $last_modified = gmdate('D, d M Y H:i:s', $cache->created) .' GMT'; - $etag = '"'. md5($last_modified) .'"'; - - // See if the client has provided the required HTTP headers: - $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE; +function drupal_page_cache_header(stdClass $cache) { + // Negotiate whether to use compression. + $page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib'); + $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; + + // Get headers set in hook_boot(). Keys are lower-case. + $hook_boot_headers = drupal_get_header(); + + // Headers generated in this function, that may be replaced or unset using + // drupal_set_headers(). Keys are mixed-case. + $default_headers = array(); + + foreach ($cache->headers as $name => $value) { + // In the case of a 304 response, certain headers must be sent, and the + // remaining may not (see RFC 2616, section 10.3.5). Do not override + // headers set in hook_boot(). + $name_lower = strtolower($name); + if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) { + drupal_set_header($name, $value); + unset($cache->headers[$name]); + } + } + + // If a cache is served from a HTTP proxy without hitting the web server, + // the boot and exit hooks cannot be fired, so only allow caching in + // proxies with aggressive caching. If the client send a session cookie, do + // not bother caching the page in a public proxy, because the cached copy + // will only be served to that particular user due to Vary: Cookie, unless + // the Vary header has been replaced or unset in hook_boot() (see below). + $max_age = variable_get('cache', CACHE_NONE) == CACHE_AGGRESSIVE && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('cache_lifetime', 0) : 0; + $default_headers['Cache-Control'] = 'public, max-age=' . $max_age; + + // Entity tag should change if the output changes. + $etag = '"' . $cache->created . '-' . intval($return_compressed) . '"'; + header('Etag: ' . $etag); + + // See if the client has provided the required HTTP headers. + $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE; $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE; if ($if_modified_since && $if_none_match && $if_none_match == $etag // etag must match - && $if_modified_since == $last_modified) { // if-modified-since must match - header('HTTP/1.1 304 Not Modified'); - // All 304 responses must send an etag if the 200 response for the same object contained an etag - header("Etag: $etag"); + && $if_modified_since == $cache->created) { // if-modified-since must match + header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified'); + drupal_send_headers($default_headers); return; } - // Send appropriate response: - header("Last-Modified: $last_modified"); - header("ETag: $etag"); - - // The following headers force validation of cache: - header("Expires: Sun, 19 Nov 1978 05:00:00 GMT"); - header("Cache-Control: must-revalidate"); - - if (variable_get('page_compression', TRUE)) { - // Determine if the browser accepts gzipped data. - if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') === FALSE && function_exists('gzencode')) { - // Strip the gzip header and run uncompress. + // Send the remaining headers. + foreach ($cache->headers as $name => $value) { + drupal_set_header($name, $value); + } + + $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created); + + // HTTP/1.0 proxies does not support the Vary header, so prevent any caching + // by sending an Expires date in the past. HTTP/1.1 clients ignores the + // Expires header if a Cache-Control: max-age= directive is specified (see RFC + // 2616, section 14.9.3). + $default_headers['Expires'] = 'Sun, 19 Nov 1978 05:00:00 GMT'; + + drupal_send_headers($default_headers); + + // Allow HTTP proxies to cache pages for anonymous users without a session + // cookie. The Vary header is used to indicates the set of request-header + // fields that fully determines whether a cache is permitted to use the + // response to reply to a subsequent request for a given URL without + // revalidation. If a Vary header has been set in hook_boot(), it is assumed + // that the module knows how to cache the page. + if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie', FALSE)) { + header('Vary: Cookie'); + } + + if ($page_compression) { + header('Vary: Accept-Encoding', FALSE); + // If page_compression is enabled, the cache contains gzipped data. + if ($return_compressed) { + header('Content-Encoding: gzip'); + } + else { + // The client does not support compression, so unzip the data in the + // cache. Strip the gzip header and run uncompress. $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8)); } - elseif (function_exists('gzencode')) { - header('Content-Encoding: gzip'); - } - } - - // Send the original request's headers. We send them one after - // another so PHP's header() function can deal with duplicate - // headers. - $headers = explode("\n", $cache->headers); - foreach ($headers as $header) { - header($header); } print $cache->data; @@ -861,7 +1057,12 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { if ($message) { if (!isset($_SESSION['messages'])) { - $_SESSION['messages'] = array(); + if (function_exists('drupal_set_session')) { + drupal_set_session('messages', array()); + } + else { + $_SESSION['messages'] = array(); + } } if (!isset($_SESSION['messages'][$type])) { @@ -985,7 +1186,7 @@ } function _drupal_bootstrap($phase) { - global $conf; + global $conf, $user; switch ($phase) { @@ -1028,23 +1229,38 @@ case DRUPAL_BOOTSTRAP_SESSION: require_once 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 exists, initialize the session. Otherwise the + // session is only started on demand in 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_LATE_PAGE_CACHE: // Initialize configuration variables, using values from settings.php if available. $conf = variable_init(isset($conf) ? $conf : array()); + $cache_mode = variable_get('cache', CACHE_DISABLED); // Get the page from the cache. - $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache(); - // If the skipping of the bootstrap hooks is not enforced, call hook_boot. - if (!$cache || $cache_mode != CACHE_AGGRESSIVE) { + $cache = $cache_mode == CACHE_DISABLED ? FALSE : page_get_cache(TRUE); + // If the skipping of the bootstrap hooks is not enforced, call hook_init. + if (!is_object($cache) || $cache_mode != CACHE_AGGRESSIVE) { // Load module handling. require_once './includes/module.inc'; bootstrap_invoke_all('boot'); } // If there is a cached page, display it. - if ($cache) { + if (is_object($cache)) { + // Destroy empty anonymous sessions. + if (drupal_session_is_started() && empty($_SESSION)) { + session_destroy(); + } + header('X-Drupal-Cache: HIT'); drupal_page_cache_header($cache); // If the skipping of the bootstrap hooks is not enforced, call hook_exit. if ($cache_mode != CACHE_AGGRESSIVE) { @@ -1053,8 +1269,20 @@ // 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 HTTP headers must be sent now, including the session + // cookie. If output buffering is enabled, the session may be started + // at any time using drupal_session_start(). + if ($cache === FALSE) { + drupal_page_header(); + drupal_session_start(); + } + else { + header('X-Drupal-Cache: MISS'); + } break; case DRUPAL_BOOTSTRAP_LANGUAGE: === modified file 'includes/cache.inc' --- includes/cache.inc 2009-04-30 00:36:53 +0000 +++ includes/cache.inc 2009-05-11 06:33:13 +0000 @@ -25,6 +25,10 @@ $cache = db_fetch_object(db_query("SELECT data, created, headers, expire, serialized FROM {". $table ."} WHERE cid = '%s'", $cid)); if (isset($cache->data)) { + if (isset($cache->headers)) { + $cache->headers = unserialize($cache->headers); + } + // If the data is permanent or we're not enforcing a minimum cache lifetime // always return the cached data. if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) { @@ -99,7 +103,9 @@ * @param $headers * A string containing HTTP header information for cached pages. */ -function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) { +function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = array()) { + $headers = serialize($headers); + $serialized = 0; if (is_object($data) || is_array($data)) { $data = serialize($data); === modified file 'includes/common.inc' --- includes/common.inc 2009-05-13 20:47:41 +0000 +++ includes/common.inc 2009-05-18 22:38:48 +0000 @@ -125,30 +125,23 @@ drupal_lookup_path('wipe'); } -/** - * Set an HTTP response header for the current page. - * - * Note: When sending a Content-Type header, always include a 'charset' type, - * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). +/* + * The function drupal_set_header() has been moved to includes/bootstrap.inc in Pressflow. */ -function drupal_set_header($header = NULL) { - // We use an array to guarantee there are no leading or trailing delimiters. - // Otherwise, header('') could get called when serving the page later, which - // ends HTTP headers prematurely on some PHP versions. - static $stored_headers = array(); - - if (strlen($header)) { - header($header); - $stored_headers[] = $header; - } - return implode("\n", $stored_headers); -} /** * Get the HTTP response headers for the current page. + * + * This function is not called by Pressflow and remains here + * only for Drupal 5/6 API compatibility. */ function drupal_get_headers() { - return drupal_set_header(); + $headers = drupal_set_header(); + $header_text = array(); + foreach ($headers as $name => $value) { + $header_text[] .= $name . ': ' . $value; + } + return implode("\n", $header_text); } /** @@ -321,9 +314,11 @@ 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_is_started()) { + // 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); @@ -1568,6 +1563,16 @@ * 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_is_started() && empty($_SESSION) && !$user->uid) { + session_destroy(); + } + elseif (!empty($_SESSION) && !drupal_session_is_started()) { + watchdog('session', '$_SESSION is non-empty yet no code has called drupal_session_start().', array(), WATCHDOG_NOTICE); + } + if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { page_set_cache(); } @@ -2584,30 +2589,46 @@ function page_set_cache() { global $user, $base_root; - if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && page_get_cache(TRUE)) { - // 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; - } - else if (zlib_get_coding_type() == FALSE) { - $data = gzencode($data, 9, FORCE_GZIP); - } - // The remaining case is 'gzip' which means the data is - // already compressed and nothing left to do but to store it. - } - ob_end_flush(); - if ($cache && $data) { - cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers()); - } - } + if (page_get_cache(FALSE)) { + $cache_page = TRUE; + $cache = (object) array( + 'cid' => $base_root . request_uri(), + 'data' => ob_get_clean(), + 'expire' => CACHE_TEMPORARY, + 'created' => $_SERVER['REQUEST_TIME'], + 'headers' => array(), + ); + // Restore preferred header names based on the lower-case names returned + // by drupal_get_header(). + $header_names = _drupal_set_preferred_header_name(); + foreach (drupal_get_header() as $name_lower => $value) { + $cache->headers[$header_names[$name_lower]] = $value; + } + 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_page = FALSE; + } + elseif (zlib_get_coding_type() == FALSE) { + $cache->data = gzencode($cache->data, 9, FORCE_GZIP); + } + // The remaining case is 'gzip' which means the data is already + // compressed and nothing left to do but to store it. + } + if ($cache_page && $cache->data) { + cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire, $cache->headers); + } + drupal_page_cache_header($cache); + } + else { + // If output buffering was enabled during bootstrap, and the headers were + // not sent in the DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE phase, send them now. + drupal_page_header(); } } + /** * Executes a cron run when called * @return === modified file 'includes/form.inc' --- includes/form.inc 2009-05-13 20:47:41 +0000 +++ includes/form.inc 2009-05-18 22:38:48 +0000 @@ -2376,7 +2376,7 @@ * foreach ($results as $result) { * $items[] = t('Loaded node %title.', array('%title' => $result)); * } - * $_SESSION['my_batch_results'] = $items; + * drupal_session_set('my_batch_results', $items); * } * @endcode */ === modified file 'includes/session.inc' --- includes/session.inc 2008-12-11 18:48:10 +0000 +++ includes/session.inc 2009-05-11 06:03:56 +0000 @@ -56,11 +56,10 @@ function sess_write($key, $value) { global $user; - // If saving of session data is disabled or if the client doesn't have a session, - // and one isn't being created ($value), do nothing. This keeps crawlers out of - // the session table. This reduces memory and server load, and gives more useful - // statistics. We can't eliminate anonymous session table rows without breaking - // the throttle module and the "Who's Online" block. + // If saving of session data is disabled, or if a new empty anonymous session + // has been started, do nothing. This keeps anonymous users, including + // crawlers, out of the session table, unless they actually have something + // stored in $_SESSION. if (!session_save_session() || ($user->uid == 0 && empty($_COOKIE[session_name()]) && empty($value))) { return TRUE; } @@ -83,6 +82,72 @@ } /** + * Propagate $_SESSION and set session cookie if not already set. This function + * should be called before writing to $_SESSION, usually via + * drupal_set_session(). + * + * @param $start + * If FALSE, the session is not actually started. This is only used by + * drupal_session_is_started(). + * @return + * TRUE if session has already been started, or FALSE if it has not. + */ +function drupal_session_start($start = TRUE) { + static $started = FALSE; + if ($start && !$started) { + $started = TRUE; + session_start(); + } + return $started; +} + +/** + * Return whether a session has been started and the $_SESSION variable is + * available. + */ +function drupal_session_is_started() { + return drupal_session_start(FALSE); +} + +/** + * Get a session variable. + * + * @param $name + * The name of the variable to get. If not supplied, all variables are returned. + * @return + * The value of the variable, or FALSE if the variable is not set. + */ +function drupal_get_session($name = NULL) { + if (is_null($name)) { + return $_SESSION; + } + elseif (isset($_SESSION[$name])) { + return $_SESSION[$name]; + } + else { + return FALSE; + } +} + +/** + * Set a session variable. The variable becomes accessible via $_SESSION[$name] + * in the current and later requests. If there is no active PHP session prior + * to the call, one is started automatically. + * + * Anonymous users generate less server load if their $_SESSION variable is + * empty, so unused entries should be unset using unset($_SESSION['foo']). + * + * @param $name + * The name of the variable to set. + * @param $value + * The value to set. + */ +function drupal_set_session($name, $value) { + drupal_session_start(); + $_SESSION[$name] = $value; +} + +/** * Called when an anonymous user becomes authenticated or vice-versa. */ function sess_regenerate() { @@ -127,6 +192,9 @@ */ function sess_destroy_sid($sid) { db_query("DELETE FROM {sessions} WHERE sid = '%s'", $sid); + // Unset cookie. + extract(session_get_cookie_params()); + setcookie(session_name(), '', time() - 3600, $path, $domain, $secure, $httponly); } /** === modified file 'modules/comment/comment.module' --- modules/comment/comment.module 2009-05-13 20:47:41 +0000 +++ modules/comment/comment.module 2009-05-18 22:38:48 +0000 @@ -1668,9 +1668,9 @@ $user = $account; } else { - $_SESSION['comment_mode'] = $mode; - $_SESSION['comment_sort'] = $order; - $_SESSION['comment_comments_per_page'] = $comments_per_page; + drupal_set_session('comment_mode', $mode); + drupal_set_session('comment_sort', $order); + drupal_set_session('comment_comments_per_page', $comments_per_page); } } === added directory 'modules/cookie_cache_bypass' === added file 'modules/cookie_cache_bypass/cookie_cache_bypass.info' --- modules/cookie_cache_bypass/cookie_cache_bypass.info 1970-01-01 00:00:00 +0000 +++ modules/cookie_cache_bypass/cookie_cache_bypass.info 2009-04-02 20:35:48 +0000 @@ -0,0 +1,3 @@ +name = Cookie Cache Bypass +description = Sets a cookie on form submission directing a reverse proxy to temporarily not serve cached pages for an anonymous user that just submitted content. +core = 6.x === added file 'modules/cookie_cache_bypass/cookie_cache_bypass.module' --- modules/cookie_cache_bypass/cookie_cache_bypass.module 1970-01-01 00:00:00 +0000 +++ modules/cookie_cache_bypass/cookie_cache_bypass.module 2009-04-02 20:35:48 +0000 @@ -0,0 +1,10 @@ + $filter) { + if (empty($_SESSION['dblog_overview_filter'])) { + drupal_set_session('dblog_overview_filter', array()); + } if (isset($form_state['values'][$name])) { $_SESSION['dblog_overview_filter'][$name] = $form_state['values'][$name]; } } break; case t('Reset'): - $_SESSION['dblog_overview_filter'] = array(); + drupal_set_session('dblog_overview_filter', array()); break; } return 'admin/reports/dblog'; === modified file 'modules/node/node.admin.inc' --- modules/node/node.admin.inc 2008-12-11 04:56:37 +0000 +++ modules/node/node.admin.inc 2009-05-11 06:03:56 +0000 @@ -323,6 +323,9 @@ $flat_options = form_options_flatten($filters[$filter]['options']); if (isset($flat_options[$form_state['values'][$filter]])) { + if (empty($_SESSION['node_overview_filter'])) { + drupal_set_session('node_overview_filter', array()); + } $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]); } } @@ -331,7 +334,7 @@ array_pop($_SESSION['node_overview_filter']); break; case t('Reset'): - $_SESSION['node_overview_filter'] = array(); + drupal_set_session($_SESSION['node_overview_filter'], array()); break; } } === modified file 'modules/openid/openid.module' --- modules/openid/openid.module 2009-04-30 00:36:53 +0000 +++ modules/openid/openid.module 2009-05-11 06:33:13 +0000 @@ -162,6 +162,10 @@ return; } + if (empty($_SESSION['openid'])) { + drupal_set_session('openid', array()); + } + // Store discovered information in the users' session so we don't have to rediscover. $_SESSION['openid']['service'] = $services[0]; // Store the claimed id === modified file 'modules/user/user.admin.inc' --- modules/user/user.admin.inc 2008-12-06 09:02:33 +0000 +++ modules/user/user.admin.inc 2009-05-11 06:03:56 +0000 @@ -101,6 +101,9 @@ // Merge an array of arrays into one if necessary. $options = $filter == 'permission' ? call_user_func_array('array_merge', $filters[$filter]['options']) : $filters[$filter]['options']; if (isset($options[$form_state['values'][$filter]])) { + if (empty($_SESSION['user_overview_filter'])) { + drupal_set_session('user_overview_filter', array()); + } $_SESSION['user_overview_filter'][] = array($filter, $form_state['values'][$filter]); } } @@ -109,7 +112,7 @@ array_pop($_SESSION['user_overview_filter']); break; case t('Reset'): - $_SESSION['user_overview_filter'] = array(); + drupal_set_session('user_overview_filter', array()); break; case t('Update'): return; === modified file 'modules/user/user.module' --- modules/user/user.module 2009-04-30 00:36:53 +0000 +++ modules/user/user.module 2009-05-11 06:33:13 +0000 @@ -1372,6 +1372,10 @@ // Regenerate the session ID to prevent against session fixation attacks. sess_regenerate(); user_module_invoke('login', $edit, $user); + if (variable_get('reverse_proxy', 0)) { + // If we have a reverse proxy, set a special cookie so the proxy would pass through the requests. + setcookie('LOGGED_IN', 'Y', $_SERVER['REQUEST_TIME'] + ini_get('session.cookie_lifetime'), '/'); + } } /** === modified file 'modules/user/user.pages.inc' --- modules/user/user.pages.inc 2008-12-06 09:02:33 +0000 +++ modules/user/user.pages.inc 2009-01-29 17:10:33 +0000 @@ -146,6 +146,12 @@ watchdog('user', 'Session closed for %name.', array('%name' => $user->name)); + if (variable_get('reverse_proxy', 0)) { + // If we are behind a reverse proxy, unset the logged in cookie by giving it + // an empty value and a time in the past + setcookie('LOGGED_IN', '', $_SERVER['REQUEST_TIME'] - 86400, '/'); + } + // Destroy the current session: session_destroy(); module_invoke_all('user', 'logout', NULL, $user); === modified file 'sites/default/default.settings.php' --- sites/default/default.settings.php 2008-12-06 09:02:33 +0000 +++ sites/default/default.settings.php 2009-01-29 17:10:33 +0000 @@ -197,6 +197,27 @@ * logging, statistics and access management systems; if you are unsure * about this setting, do not have a reverse proxy, or Drupal operates in * a shared hosting environment, this setting should be set to disabled. + * + * Configuration for Squid + * ... + * acl cookie_logged_in_set rep_header Set-Cookie LOGGED_IN=Y + * cache deny cookie_logged_in_set + * acl cookie_logged_in_out rep_header Cookie LOGGED_IN=Y + * cache deny cookie_logged_in_out + * acl cookie_logged_in req_header Cookie LOGGED_IN=Y + * cache deny cookie_logged_in + * ... + * + * Configuration for Varnish + * + * sub vcl_recv { + * ... + * if (req.http.Cookie && req.http.Cookie ~ "LOGGED_IN=Y") { + * pass; + * } + * ... + * } + * */ # 'reverse_proxy' => TRUE, /** === modified file 'update.php' --- update.php 2009-04-30 00:36:53 +0000 +++ update.php 2009-05-11 06:54:00 +0000 @@ -617,6 +617,7 @@ // Access check: if (!empty($update_free_access) || $user->uid == 1) { + drupal_session_start(); include_once './includes/install.inc'; include_once './includes/batch.inc';