Index: .htaccess
===================================================================
RCS file: /cvs/drupal/drupal/.htaccess,v
retrieving revision 1.99
diff -u -9 -p -r1.99 .htaccess
--- .htaccess 9 Jan 2009 02:49:01 -0000 1.99
+++ .htaccess 17 Mar 2009 20:13:26 -0000
@@ -42,20 +42,22 @@ DirectoryIndex index.php
# Requires mod_expires to be enabled.
# Enable expirations.
ExpiresActive On
# 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.
RewriteEngine on
# If your site can be accessed both with and without the 'www.' prefix, you
# can use one of the following settings to redirect users to your preferred
# URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
Index: CHANGELOG.txt
===================================================================
RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v
retrieving revision 1.300
diff -u -9 -p -r1.300 CHANGELOG.txt
--- CHANGELOG.txt 15 Mar 2009 01:53:16 -0000 1.300
+++ CHANGELOG.txt 17 Mar 2009 20:13:27 -0000
@@ -33,18 +33,20 @@ Drupal 7.0, xxxx-xx-xx (development vers
* Redesigned the add content type screen.
* Highlight duplicate URL aliases.
* Renamed "input formats" to "text formats".
* Added configurable ability for users to cancel their own accounts.
* Added optional filter that can use [internal:node/123] to link to internal
pages.
- Performance:
* Improved performance on uncached page views by loading multiple core
objects in a single database query.
+ * Improved support for HTTP proxies (including reverse proxies), allowing
+ anonymous pageviews to be served entirely from the proxy.
- Documentation:
* Hook API documentation now included in Drupal core.
- News aggregator:
* Added OPML import functionality for RSS feeds.
* Optionally, RSS feeds may be configured to not automatically generate feed blocks.
- Search:
* Added support for language-aware searches.
- Aggregator:
* Introduced architecture that allows pluggable parsers and processors for
Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.271
diff -u -9 -p -r1.271 bootstrap.inc
--- includes/bootstrap.inc 1 Mar 2009 09:32:17 -0000 1.271
+++ includes/bootstrap.inc 17 Mar 2009 20:13:27 -0000
@@ -740,84 +740,272 @@ function drupal_load($type, $name) {
$files[$type][$name] = TRUE;
return TRUE;
}
return FALSE;
}
/**
+ * 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 = ':status';
+ }
+ else {
+ _drupal_set_preferred_header_name($name);
+ $name = strtolower($name);
+ }
+
+ if (!isset($value)) {
+ $headers[$name] = FALSE;
+ }
+ elseif (isset($headers[$name]) && $append) {
+ // Multiple headers with identical names may be combined using comma (RFC
+ // 2616, section 4.2).
+ $headers[$name] .= ',' . $value;
+ }
+ else {
+ $headers[$name] = $value;
+ }
+ drupal_send_headers(array($name => $headers[$name]), 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, 19 Nov 1978 05:00:00 GMT',
+ 'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
+ 'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',
+ 'ETag' => '"' . 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.
- *
- */
-function drupal_page_cache_header($cache) {
- // Create entity tag based on cache update time.
- $etag = '"' . md5($cache->created) . '"';
+ * 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(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_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 == $cache->created) { // if-modified-since must match
header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
- // All 304 responses must send an etag if the 200 response for the same object contained an etag
- header("Etag: $etag");
+ drupal_send_headers($default_headers);
return;
}
- // Send appropriate response:
- header("Last-Modified: " . gmdate(DATE_RFC1123, $cache->created));
- 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.
- $cache->data = gzinflate(substr(substr($cache->data, 10), 0, -8));
- }
- elseif (function_exists('gzencode')) {
+ // 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'])) {
+ 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');
}
- }
-
- // 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);
+ 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));
+ }
}
print $cache->data;
}
/**
* Unserializes and appends elements from a serialized string.
*
* @param $obj
@@ -1190,35 +1378,41 @@ function _drupal_bootstrap($phase) {
require_once DRUPAL_ROOT . '/includes/module.inc';
module_invoke_all('boot');
}
// If there is a cached page, display it.
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) {
module_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
+ // 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:
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/cache.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache.inc,v
retrieving revision 1.28
diff -u -9 -p -r1.28 cache.inc
--- includes/cache.inc 3 Feb 2009 12:30:14 -0000 1.28
+++ includes/cache.inc 17 Mar 2009 20:13:27 -0000
@@ -24,45 +24,42 @@ function cache_get($cid, $table = 'cache
variable_set('cache_flush', 0);
// Time to flush old cache data
db_delete($table)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', $cache_flush, '<=')
->execute();
}
$cache = db_query("SELECT data, created, headers, expire, serialized FROM {" . $table . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
- if (isset($cache->data)) {
- // 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)) {
- if ($cache->serialized) {
- $cache->data = unserialize($cache->data);
- }
- }
- // If enforcing a minimum cache lifetime, validate that the data is
- // currently valid for this user before we return it by making sure the
- // cache entry was created before the timestamp in the current session's
- // cache timer. The cache variable is loaded into the $user object by
- // _sess_read() in session.inc.
- else {
- if ($user->cache > $cache->created) {
- // This cache data is too old and thus not valid for us, ignore it.
- return FALSE;
- }
- else {
- if ($cache->serialized) {
- $cache->data = unserialize($cache->data);
- }
- }
- }
- return $cache;
+
+ if (!isset($cache->data)) {
+ return FALSE;
+ }
+
+ // If enforcing a minimum cache lifetime, validate that the data is
+ // currently valid for this user before we return it by making sure the cache
+ // entry was created before the timestamp in the current session's cache
+ // timer. The cache variable is loaded into the $user object by _sess_read()
+ // in session.inc. 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) && $user->cache > $cache->created) {
+ // This cache data is too old and thus not valid for us, ignore it.
+ return FALSE;
}
- return FALSE;
+
+ if ($cache->serialized) {
+ $cache->data = unserialize($cache->data);
+ }
+ if (isset($cache->headers)) {
+ $cache->headers = unserialize($cache->headers);
+ }
+
+ return $cache;
}
/**
* Store data in the persistent cache.
*
* The persistent cache is split up into four database
* tables. Contributed modules can add additional tables.
*
* 'cache_page': This table stores generated pages for anonymous
@@ -98,24 +95,24 @@ function cache_get($cid, $table = 'cache
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
* explicitly told to using cache_clear_all() with a cache ID.
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
* general cache wipe.
* - A Unix timestamp: Indicates that the item should be kept at least until
* the given time, after which it behaves like CACHE_TEMPORARY.
* @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, array $headers = NULL) {
$fields = array(
'serialized' => 0,
'created' => REQUEST_TIME,
'expire' => $expire,
- 'headers' => $headers,
+ 'headers' => isset($headers) ? serialize($headers) : NULL,
);
if (!is_string($data)) {
$fields['data'] = serialize($data);
$fields['serialized'] = 1;
}
else {
$fields['data'] = $data;
$fields['serialized'] = 0;
}
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.870
diff -u -9 -p -r1.870 common.inc
--- includes/common.inc 28 Feb 2009 07:36:06 -0000 1.870
+++ includes/common.inc 17 Mar 2009 20:13:27 -0000
@@ -149,44 +149,18 @@ function drupal_get_html_head() {
/**
* Reset the static variable which holds the aliases mapped for this request.
*/
function drupal_clear_path_cache() {
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).
- */
-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.
- */
-function drupal_get_headers() {
- return drupal_set_header();
-}
-
-/**
* Add a feed URL for the current page.
*
* This function can be called as long the HTML header hasn't been sent.
*
* @param $url
* A url for the feed.
* @param $title
* The title of the feed.
*/
@@ -351,29 +325,29 @@ function drupal_goto($path = '', $query
// drupal_goto() call gets executed upon redirection.
exit();
}
/**
* Generates a site offline message.
*/
function drupal_site_offline() {
drupal_maintenance_theme();
- drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service unavailable');
+ drupal_set_header('503 Service unavailable');
drupal_set_title(t('Site offline'));
print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
}
/**
* Generates a 404 error if the request can not be handled.
*/
function drupal_not_found() {
- drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
+ drupal_set_header('404 Not Found');
watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
// Keep old path for reference.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
}
$path = drupal_get_normal_path(variable_get('site_404', ''));
@@ -395,19 +369,19 @@ function drupal_not_found() {
$page['#show_blocks'] = FALSE;
print drupal_render_page($page);
}
/**
* Generates a 403 error if the request is not allowed.
*/
function drupal_access_denied() {
- drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
+ drupal_set_header('403 Forbidden');
watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
// Keep old path for reference.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
}
$path = drupal_get_normal_path(variable_get('site_403', ''));
if ($path && $path != $_GET['q']) {
@@ -812,19 +786,19 @@ function _drupal_log_error($error, $fata
try {
watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
}
catch (Exception $e) {
$new_error = _drupal_decode_exception($e);
drupal_set_message(t('%type: %message in %function (line %line of %file).', $new_error), 'error');
}
if ($fatal) {
- drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' Service unavailable');
+ drupal_set_header('503 Service unavailable');
drupal_set_title(t('Error'));
if (!defined('MAINTENANCE_MODE') && drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
// To conserve CPU and bandwidth, omit the blocks.
$page = drupal_get_page(t('The website encountered an unexpected error. Please try again later.'));
$page['#show_blocks'] = FALSE;
print drupal_render_page($page);
}
else {
print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
@@ -2811,19 +2785,19 @@ function drupal_to_js($var) {
*
* This function should be used for JavaScript callback functions returning
* data in JSON format. It sets the header for JavaScript output.
*
* @param $var
* (optional) If set, the variable will be converted to JSON and output.
*/
function drupal_json($var = NULL) {
// We are returning JavaScript, so tell the browser.
- drupal_set_header('Content-Type: text/javascript; charset=utf-8');
+ drupal_set_header('Content-Type', 'text/javascript; charset=utf-8');
if (isset($var)) {
echo drupal_to_js($var);
}
}
/**
* Wrapper around urlencode() which avoids Apache quirks.
*
@@ -2979,19 +2953,19 @@ function _drupal_bootstrap_full() {
require_once DRUPAL_ROOT . '/includes/image.inc';
require_once DRUPAL_ROOT . '/includes/form.inc';
require_once DRUPAL_ROOT . '/includes/mail.inc';
require_once DRUPAL_ROOT . '/includes/actions.inc';
// Set the Drupal custom error handler.
set_error_handler('_drupal_error_handler');
set_exception_handler('_drupal_exception_handler');
// Emit the correct charset HTTP header.
- drupal_set_header('Content-Type: text/html; charset=utf-8');
+ drupal_set_header('Content-Type', 'text/html; charset=utf-8');
// Detect string handling method
unicode_check();
// Undo magic quotes
fix_gpc_magic();
// Load all enabled modules
module_load_all();
// Let all modules take action before menu system handles the request
// We do not want this while running update.php.
@@ -3011,36 +2985,47 @@ function _drupal_bootstrap_full() {
* 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 (page_get_cache(FALSE)) {
- $cache = TRUE;
- $data = ob_get_contents();
+ $cache_page = TRUE;
+ $cache = (object) array(
+ 'cid' => $base_root . request_uri(),
+ 'data' => ob_get_clean(),
+ 'expire' => CACHE_TEMPORARY,
+ 'created' => REQUEST_TIME,
+ 'headers' => drupal_get_header(),
+ );
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;
+ $cache_page = FALSE;
}
elseif (zlib_get_coding_type() == FALSE) {
- $data = gzencode($data, 9, FORCE_GZIP);
+ $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.
}
- ob_end_flush();
- if ($cache && $data) {
- cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
+ 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
* Returns TRUE if ran successfully
*/
function drupal_cron_run() {
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.164
diff -u -9 -p -r1.164 file.inc
--- includes/file.inc 10 Mar 2009 09:47:42 -0000 1.164
+++ includes/file.inc 17 Mar 2009 20:13:27 -0000
@@ -1306,25 +1306,22 @@ function file_unmanaged_save_data($data,
* String specifying the file path to transfer.
* @param $headers
* An array of HTTP headers to send along with file.
*/
function file_transfer($source, $headers) {
if (ob_get_level()) {
ob_end_clean();
}
- foreach ($headers as $header) {
- // To prevent HTTP header injection, we delete new lines that are
- // not followed by a space or a tab.
- // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
- $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
- drupal_set_header($header);
+ foreach ($headers as $name => $value) {
+ drupal_set_header($name, $value);
}
+ drupal_send_headers();
$source = file_create_path($source);
// Transfer file in 1024 byte chunks to save memory usage.
if ($fd = fopen($source, 'rb')) {
while (!feof($fd)) {
print fread($fd, 1024);
}
fclose($fd);
Index: includes/theme.maintenance.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.maintenance.inc,v
retrieving revision 1.21
diff -u -9 -p -r1.21 theme.maintenance.inc
--- includes/theme.maintenance.inc 24 Nov 2008 10:41:39 -0000 1.21
+++ includes/theme.maintenance.inc 17 Mar 2009 20:13:27 -0000
@@ -102,19 +102,19 @@ function theme_task_list($items, $active
/**
* Generate a themed installation page.
*
* Note: this function is not themeable.
*
* @param $content
* The page content to show.
*/
function theme_install_page($content) {
- drupal_set_header('Content-Type: text/html; charset=utf-8');
+ drupal_set_header('Content-Type', 'text/html; charset=utf-8');
// Assign content.
$variables['content'] = $content;
// Delay setting the message variable so it can be processed below.
$variables['show_messages'] = FALSE;
// The maintenance preprocess function is recycled here.
template_preprocess_maintenance_page($variables);
// Special handling of error messages
@@ -156,19 +156,19 @@ function theme_install_page($content) {
*
* @param $content
* The page content to show.
* @param $show_messages
* Whether to output status and error messages.
* FALSE can be useful to postpone the messages to a subsequent page.
*/
function theme_update_page($content, $show_messages = TRUE) {
// Set required headers.
- drupal_set_header('Content-Type: text/html; charset=utf-8');
+ drupal_set_header('Content-Type', 'text/html; charset=utf-8');
// Assign content and show message flag.
$variables['content'] = $content;
$variables['show_messages'] = $show_messages;
// The maintenance preprocess function is recycled here.
template_preprocess_maintenance_page($variables);
// Special handling of warning messages.
$messages = drupal_set_message();
Index: modules/aggregator/aggregator.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.pages.inc,v
retrieving revision 1.24
diff -u -9 -p -r1.24 aggregator.pages.inc
--- modules/aggregator/aggregator.pages.inc 3 Feb 2009 18:55:29 -0000 1.24
+++ modules/aggregator/aggregator.pages.inc 17 Mar 2009 20:13:27 -0000
@@ -365,19 +365,19 @@ function aggregator_page_rss() {
* Theme the RSS output.
*
* @param $feeds
* An array of the feeds to theme.
* @param $category
* A common category, if any, for all the feeds.
* @ingroup themeable
*/
function theme_aggregator_page_rss($feeds, $category = NULL) {
- drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+ drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8');
$items = '';
$feed_length = variable_get('feed_item_length', 'teaser');
foreach ($feeds as $feed) {
switch ($feed_length) {
case 'teaser':
$teaser = node_teaser($feed->description);
if ($teaser != $feed->description) {
$teaser .= '' . t('read more') . "
\n";
@@ -425,19 +425,19 @@ function aggregator_page_opml($cid = NUL
/**
* Theme the OPML feed output.
*
* @param $feeds
* An array of the feeds to theme.
* @ingroup themeable
*/
function theme_aggregator_page_opml($feeds) {
- drupal_set_header('Content-Type: text/xml; charset=utf-8');
+ drupal_set_header('Content-Type', 'text/xml; charset=utf-8');
$output = "\n";
$output .= "\n";
$output .= "\n";
$output .= '' . check_plain(variable_get('site_name', 'Drupal')) . "\n";
$output .= '' . gmdate('r') . "\n";
$output .= "\n";
$output .= "\n";
foreach ($feeds as $feed) {
Index: modules/aggregator/aggregator_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator_test.module,v
retrieving revision 1.2
diff -u -9 -p -r1.2 aggregator_test.module
--- modules/aggregator/aggregator_test.module 15 Mar 2009 18:59:05 -0000 1.2
+++ modules/aggregator/aggregator_test.module 17 Mar 2009 20:13:27 -0000
@@ -27,33 +27,33 @@ function aggregator_test_feed($use_last_
$last_modified = strtotime('Sun, 19 Nov 1978 05:00:00 GMT');
$etag = md5($last_modified);
$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;
// Send appropriate response. We respond with a 304 not modified on either
// etag or on last modified.
if ($use_last_modified) {
- drupal_set_header("Last-Modified: " . gmdate(DATE_RFC1123, $last_modified));
+ drupal_set_header('Last-Modified', gmdate(DATE_RFC1123, $last_modified));
}
if ($use_etag) {
- drupal_set_header("ETag: " .$etag);
+ drupal_set_header('ETag', $etag);
}
// Return 304 not modified if either last modified or etag match.
if ($last_modified == $if_modified_since || $etag == $if_none_match) {
- drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
+ drupal_set_header('304 Not Modified');
return;
}
// The following headers force validation of cache:
- drupal_set_header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
- drupal_set_header("Cache-Control: must-revalidate");
- drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+ drupal_set_header('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
+ drupal_set_header('Cache-Control', 'must-revalidate');
+ drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8');
// Read actual feed from file.
$file_name = DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator_test_rss091.xml';
$handle = fopen($file_name, 'r');
$feed = fread($handle, filesize($file_name));
fclose($handle);
print $feed;
}
Index: modules/blogapi/blogapi.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blogapi/blogapi.module,v
retrieving revision 1.146
diff -u -9 -p -r1.146 blogapi.module
--- modules/blogapi/blogapi.module 17 Mar 2009 12:41:54 -0000 1.146
+++ modules/blogapi/blogapi.module 17 Mar 2009 20:13:27 -0000
@@ -843,19 +843,19 @@ function blogapi_init() {
}
function blogapi_rsd() {
global $base_url;
$xmlrpc = $base_url . '/xmlrpc.php';
$base = url('', array('absolute' => TRUE));
$blogid = 1; # until we figure out how to handle multiple bloggers
- drupal_set_header('Content-Type: application/rsd+xml; charset=utf-8');
+ drupal_set_header('Content-Type', 'application/rsd+xml; charset=utf-8');
print <<<__RSD__
Drupal
http://drupal.org/
$base
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1031
diff -u -9 -p -r1.1031 node.module
--- modules/node/node.module 14 Mar 2009 23:01:36 -0000 1.1031
+++ modules/node/node.module 17 Mar 2009 20:13:27 -0000
@@ -1987,19 +1987,19 @@ function node_feed($nids = FALSE, $chann
'language' => $language->language
);
$channel = array_merge($channel_defaults, $channel);
$output = "\n";
$output .= "\n";
$output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
$output .= "\n";
- drupal_set_header('Content-Type: application/rss+xml; charset=utf-8');
+ drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8');
print $output;
}
/**
* Construct a drupal_render() style array from an array of loaded nodes.
*
* @param $nodes
* An array of nodes as returned by node_load_multiple().
* @param $teaser
Index: modules/simpletest/tests/bootstrap.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/bootstrap.test,v
retrieving revision 1.12
diff -u -9 -p -r1.12 bootstrap.test
--- modules/simpletest/tests/bootstrap.test 31 Jan 2009 16:50:57 -0000 1.12
+++ modules/simpletest/tests/bootstrap.test 17 Mar 2009 20:13:27 -0000
@@ -84,56 +84,99 @@ class BootstrapPageCacheTestCase extends
function getInfo() {
return array(
'name' => t('Page cache test'),
'description' => t('Enable the page cache and test it with conditional HTTP requests.'),
'group' => t('Bootstrap')
);
}
+ function setUp() {
+ parent::setUp('system_test');
+ }
+
/**
- * Enable cache and examine HTTP headers.
+ * Test support for requests containing If-Modified-Since and If-None-Match headers.
*/
- function testPageCache() {
+ function testConditionalRequests() {
variable_set('cache', CACHE_NORMAL);
// Fill the cache.
$this->drupalGet('');
$this->drupalHead('');
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
$etag = $this->drupalGetHeader('ETag');
- $this->assertTrue($etag, t('An ETag header was sent, indicating that page was cached.'));
$last_modified = $this->drupalGetHeader('Last-Modified');
$this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
$this->assertResponse(304, t('Conditional request returned 304 Not Modified.'));
$this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC822, strtotime($last_modified)), 'If-None-Match: ' . $etag));
$this->assertResponse(304, t('Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.'));
$this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC850, strtotime($last_modified)), 'If-None-Match: ' . $etag));
$this->assertResponse(304, t('Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.'));
$this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified));
$this->assertResponse(200, t('Conditional request without If-None-Match returned 200 OK.'));
- $this->assertTrue($this->drupalGetHeader('ETag'), t('An ETag header was sent, indicating that page was cached.'));
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
$this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC1123, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag));
$this->assertResponse(200, t('Conditional request with new a If-Modified-Since date newer than Last-Modified returned 200 OK.'));
- $this->assertTrue($this->drupalGetHeader('ETag'), t('An ETag header was sent, indicating that page was cached.'));
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
$user = $this->drupalCreateUser();
$this->drupalLogin($user);
$this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag));
$this->assertResponse(200, t('Conditional request returned 200 OK for authenticated user.'));
- $this->assertFalse($this->drupalGetHeader('ETag'), t('An ETag header was not sent, indicating that page was not cached.'));
+ $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Absense of Page was not cached.'));
}
+ /**
+ * Test cache headers.
+ */
+ function testPageCache() {
+ variable_set('cache', CACHE_NORMAL);
+
+ // Fill the cache.
+ $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.'));
+ $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', t('Vary header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', t('Cache-Control header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.'));
+
+ // Check cache.
+ $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
+ $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', t('Vary: Cookie header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', t('Cache-Control header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.'));
+
+ // Check replacing default headers.
+ $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT')));
+ $this->assertEqual($this->drupalGetHeader('Expires'), 'Fri, 19 Nov 2008 05:00:00 GMT', t('Default header was replaced.'));
+ $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Vary', 'value' => 'User-Agent')));
+ $this->assertEqual($this->drupalGetHeader('Vary'), 'User-Agent,Accept-Encoding', t('Default header was replaced.'));
+
+ // Check that authenticated users bypass the cache.
+ $user = $this->drupalCreateUser();
+ $this->drupalLogin($user);
+ $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar')));
+ $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.'));
+ $this->assertFalse($this->drupalGetHeader('Vary'), t('Vary header was not sent.'));
+ $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', t('Cache-Control header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.'));
+ $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.'));
+
+ }
}
class BootstrapVariableTestCase extends DrupalWebTestCase {
function setUp() {
parent::setUp('system_test');
}
function getInfo() {
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.30
diff -u -9 -p -r1.30 common.test
--- modules/simpletest/tests/common.test 28 Feb 2009 07:36:06 -0000 1.30
+++ modules/simpletest/tests/common.test 17 Mar 2009 20:13:27 -0000
@@ -599,33 +599,33 @@ class DrupalErrorHandlerUnitTest extends
/**
* Test the error handler.
*/
function testErrorHandler() {
$error_notice = array(
'%type' => 'Notice',
'%message' => 'Undefined variable: bananas',
'%function' => 'system_test_generate_warnings()',
- '%line' => 184,
+ '%line' => 194,
'%file' => realpath('modules/simpletest/tests/system_test.module'),
);
$error_warning = array(
'%type' => 'Warning',
'%message' => 'Division by zero',
'%function' => 'system_test_generate_warnings()',
- '%line' => 186,
+ '%line' => 196,
'%file' => realpath('modules/simpletest/tests/system_test.module'),
);
$error_user_notice = array(
'%type' => 'User notice',
'%message' => 'Drupal is awesome',
'%function' => 'system_test_generate_warnings()',
- '%line' => 188,
+ '%line' => 198,
'%file' => realpath('modules/simpletest/tests/system_test.module'),
);
// Set error reporting to collect notices.
variable_set('error_level', 2);
$this->drupalGet('system-test/generate-warnings');
$this->assertErrorMessage($error_notice);
$this->assertErrorMessage($error_warning);
$this->assertErrorMessage($error_user_notice);
@@ -647,26 +647,26 @@ class DrupalErrorHandlerUnitTest extends
/**
* Test the exception handler.
*/
function testExceptionHandler() {
$error_exception = array(
'%type' => 'Exception',
'%message' => 'Drupal is awesome',
'%function' => 'system_test_trigger_exception()',
- '%line' => 197,
+ '%line' => 207,
'%file' => realpath('modules/simpletest/tests/system_test.module'),
);
$error_pdo_exception = array(
'%type' => 'PDOException',
'%message' => 'SQLSTATE',
'%function' => 'system_test_trigger_pdo_exception()',
- '%line' => 205,
+ '%line' => 215,
'%file' => realpath('modules/simpletest/tests/system_test.module'),
);
$this->drupalGet('system-test/trigger-exception');
$this->assertErrorMessage($error_exception);
$this->drupalGet('system-test/trigger-pdo-exception');
// We cannot use assertErrorMessage() since the extact error reported
// varies from database to database. Check for the error keyword 'SQLSTATE'.
Index: modules/simpletest/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v
retrieving revision 1.26
diff -u -9 -p -r1.26 file.test
--- modules/simpletest/tests/file.test 14 Mar 2009 23:01:37 -0000 1.26
+++ modules/simpletest/tests/file.test 17 Mar 2009 20:13:27 -0000
@@ -1893,19 +1893,19 @@ class FileDownloadTest extends FileTestC
function testPrivateFileTransfer() {
// Set file downloads to private so handler functions get called.
variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE);
// Create a file.
$file = $this->createFile();
$url = file_create_url($file->filename);
// Set file_test access header to allow the download.
- file_test_set_return('download', array('x-foo: Bar'));
+ file_test_set_return('download', array('x-foo' => 'Bar'));
$this->drupalHead($url);
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-foo'] , 'Bar', t('Found header set by file_test module on private download.'));
$this->assertResponse(200, t('Correctly allowed access to a file when file_test provides headers.'));
// Deny access to all downloads via a -1 header.
file_test_set_return('download', -1);
$this->drupalHead($url);
$this->assertResponse(403, t('Correctly denied access to a file when file_test sets the header to -1.'));
Index: modules/simpletest/tests/session.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/session.test,v
retrieving revision 1.10
diff -u -9 -p -r1.10 session.test
--- modules/simpletest/tests/session.test 19 Jan 2009 10:46:51 -0000 1.10
+++ modules/simpletest/tests/session.test 17 Mar 2009 20:13:27 -0000
@@ -157,19 +157,19 @@ class SessionTestCase extends DrupalWebT
variable_set('cache', CACHE_NORMAL);
// During this request the session is destroyed in drupal_page_footer(),
// and the session cookie is unset.
$this->drupalGet('');
$this->assertSessionCookie(TRUE);
$this->assertSessionStarted(TRUE);
$this->assertSessionEmpty(TRUE);
- $this->assertFalse($this->drupalGetHeader('ETag'), t('Page was not cached.'));
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.'));
// When PHP deletes a cookie, it sends "Set-Cookie: cookiename=deleted;
// expires=..."
$this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.'));
// Verify that the session cookie was actually deleted.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionStarted(FALSE);
$this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.'));
@@ -179,36 +179,36 @@ class SessionTestCase extends DrupalWebT
$this->assertSessionCookie(FALSE);
$this->assertSessionStarted(FALSE);
$this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.'));
// Display the message.
$this->drupalGet('');
$this->assertSessionCookie(TRUE);
$this->assertSessionStarted(TRUE);
$this->assertSessionEmpty(FALSE);
- $this->assertFalse($this->drupalGetHeader('ETag'), t('Page was not cached.'));
+ $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.'));
$this->assertText(t('This is a dummy message.'), t('Message was displayed.'));
// During this request the session is destroyed in _drupal_bootstrap(),
// and the session cookie is unset.
$this->drupalGet('');
$this->assertSessionCookie(TRUE);
$this->assertSessionStarted(TRUE);
$this->assertSessionEmpty(TRUE);
- $this->assertTrue($this->drupalGetHeader('ETag'), t('Page was cached.'));
+ $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));
$this->assertNoText(t('This is a dummy message.'), t('Message was not cached.'));
$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->assertSessionStarted(FALSE);
- $this->assertTrue($this->drupalGetHeader('ETag'), t('Page was 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 modifying $_SESSION without having started a session
// generates a watchdog message, and that no messages have been generated
// so far.
$this->assertEqual($this->getWarningCount(), 0, t('No watchdog messages have been generated'));
$this->drupalGet('/session-test/set-not-started');
$this->assertSessionCookie(FALSE);
$this->assertSessionStarted(FALSE);
Index: modules/simpletest/tests/system_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/system_test.module,v
retrieving revision 1.8
diff -u -9 -p -r1.8 system_test.module
--- modules/simpletest/tests/system_test.module 9 Dec 2008 11:09:26 -0000 1.8
+++ modules/simpletest/tests/system_test.module 17 Mar 2009 20:13:27 -0000
@@ -11,18 +11,23 @@ function system_test_menu() {
'type' => MENU_CALLBACK,
);
$items['system-test/redirect/%'] = array(
'title' => 'Redirect',
'page callback' => 'system_test_redirect',
'page arguments' => array(2),
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
+ $items['system-test/set-header'] = array(
+ 'page callback' => 'system_test_set_header',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ );
$items['system-test/redirect-noscheme'] = array(
'page callback' => 'system_test_redirect_noscheme',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
$items['system-test/redirect-noparse'] = array(
'page callback' => 'system_test_redirect_noparse',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
@@ -89,18 +94,23 @@ function system_test_basic_auth_page() {
function system_test_redirect($code) {
$code = (int)$code;
if ($code != 200) {
header("Location: " . url('system-test/redirect/200', array('absolute' => TRUE)), TRUE, $code);
exit;
}
return '';
}
+function system_test_set_header() {
+ drupal_set_header($_GET['name'], $_GET['value']);
+ return t('The following header was set: %name: %value', array('%name' => $_GET['name'], '%value' => $_GET['value']));
+}
+
function system_test_redirect_noscheme() {
header("Location: localhost/path", TRUE, 301);
exit;
}
function system_test_redirect_noparse() {
header("Location: http:///path", TRUE, 301);
exit;
}
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.673
diff -u -9 -p -r1.673 system.module
--- modules/system/system.module 17 Mar 2009 15:26:29 -0000 1.673
+++ modules/system/system.module 17 Mar 2009 20:13:27 -0000
@@ -2277,19 +2277,19 @@ function theme_meta_generator_html($vers
drupal_set_html_head('');
}
/**
* Send Drupal and the major version number in the HTTP headers.
*
* @ingroup themeable
*/
function theme_meta_generator_header($version = VERSION) {
- drupal_set_header('X-Generator: Drupal ' . $version . ' (http://drupal.org)');
+ drupal_set_header('X-Generator', 'Drupal ' . $version . ' (http://drupal.org)');
}
/**
* Implementation of hook_image_toolkits().
*/
function system_image_toolkits() {
return array(
'gd' => array(
'title' => t('GD2 image manipulation toolkit'),