Commit 5c72eb29 authored by Dries's avatar Dries

- Patch #327269 by c960657: when drupal_page_cache_header() compares the...

- Patch #327269 by c960657: when drupal_page_cache_header() compares the client's If-Modified-Since header to $cache->created, it assumed a certain date format.  However, HTTP/1.1 allows several variations of the date format, i.e. the same time may be represented in slightly different ways. If the client sends the date in a different format than the one generated by Drupal, it would never receive a 304 Not Modified response.  Also added a good amount of tests for the drupal_page_cache_header() code.
parent 3aaffd33
......@@ -730,17 +730,16 @@ function drupal_page_header() {
*
*/
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) . '"';
// Create entity tag based on cache update time.
$etag = '"' . md5($cache->created) . '"';
// 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;
// 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
&& $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");
......@@ -748,7 +747,7 @@ function drupal_page_cache_header($cache) {
}
// Send appropriate response:
header("Last-Modified: $last_modified");
header("Last-Modified: " . gmdate(DATE_RFC1123, $cache->created));
header("ETag: $etag");
// The following headers force validation of cache:
......
......@@ -1001,16 +1001,19 @@ protected function parse() {
* Drupal path or URL to load into internal browser
* @param $options
* Options to be forwarded to url().
* @param $headers
* An array containing additional HTTP request headers, each formatted as
* "name: value".
* @return
* The retrieved HTML string, also available as $this->drupalGetContent()
*/
protected function drupalGet($path, $options = array()) {
protected function drupalGet($path, Array $options = array(), Array $headers = array()) {
$options['absolute'] = TRUE;
// We re-using a CURL connection here. If that connection still has certain
// options set, it might change the GET into a POST. Make sure we clear out
// previous options.
$out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE));
$out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers));
$this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
// Replace original page output with new output from redirected page(s).
......@@ -1051,8 +1054,11 @@ protected function drupalGet($path, $options = array()) {
* Value of the submit button.
* @param $options
* Options to be forwarded to url().
* @param $headers
* An array containing additional HTTP request headers, each formatted as
* "name: value".
*/
protected function drupalPost($path, $edit, $submit, $options = array()) {
protected function drupalPost($path, $edit, $submit, Array $options = array(), Array $headers = array()) {
$submit_matches = FALSE;
if (isset($path)) {
$html = $this->drupalGet($path, $options);
......@@ -1092,7 +1098,7 @@ protected function drupalPost($path, $edit, $submit, $options = array()) {
}
$post = implode('&', $post);
}
$out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post));
$out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
// Ensure that any changes to variables in the other thread are picked up.
$this->refreshVariables();
......@@ -1141,12 +1147,15 @@ protected function checkForMetaRefresh() {
* Drupal path or URL to load into internal browser
* @param $options
* Options to be forwarded to url().
* @param $headers
* An array containing additional HTTP request headers, each formatted as
* "name: value".
* @return
* The retrieved headers, also available as $this->drupalGetContent()
*/
protected function drupalHead($path, Array $options = array()) {
protected function drupalHead($path, Array $options = array(), Array $headers = array()) {
$options['absolute'] = TRUE;
$out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options)));
$out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_HTTPHEADER => $headers));
$this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up.
return $out;
}
......
......@@ -89,7 +89,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
function getInfo() {
return array(
'name' => t('Page cache test'),
'description' => t('Enable the page cache, submit a HEAD request and examine headers.'),
'description' => t('Enable the page cache and test it with conditional HTTP requests.'),
'group' => t('Bootstrap')
);
}
......@@ -98,13 +98,38 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase {
* Enable cache and examine HTTP headers.
*/
function testPageCache() {
global $base_url;
variable_set('cache', 1);
variable_set('cache', CACHE_NORMAL);
// Fill the cache.
$this->drupalGet($base_url);
$this->drupalGet('');
$this->drupalHead('');
$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->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->drupalHead($base_url);
$this->assertTrue($this->drupalGetHeader('ETag') !== FALSE, t('Verify presence of ETag header indicating that page caching is enabled.'));
$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.'));
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment