diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index e5d043792f279d8f37da75edc5442439e790e8c1..6ebf9d8dfcf8bb5151a573eceb777b46ad0146f9 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -27,6 +27,13 @@ class DrupalWebTestCase { */ protected $curlHandle; + /** + * The headers of the page currently loaded in the internal browser. + * + * @var Array + */ + protected $headers; + /** * The content of the page currently loaded in the internal browser. * @@ -923,6 +930,7 @@ protected function curlExec($curl_options) { $this->curlInitialize(); $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options); + $this->headers = array(); $this->drupalSetContent(curl_exec($this->curlHandle), curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL)); $this->assertTrue($this->content !== FALSE, t('!method to !url, response is !length bytes.', array('!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), '!url' => $url, '!length' => strlen($this->content))), t('Browser')); return $this->drupalGetContent(); @@ -939,6 +947,7 @@ protected function curlExec($curl_options) { * An header. */ protected function curlHeaderCallback($curlHandler, $header) { + $this->headers[] = $header; // Errors are being sent via X-Drupal-Assertion-* headers, // generated by _drupal_log_error() in the exact form required // by DrupalWebTestCase::error(). @@ -1001,7 +1010,7 @@ protected function drupalGet($path, $options = array()) { // 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_HEADER => FALSE, CURLOPT_NOBODY => FALSE)); + $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($path, $options), CURLOPT_NOBODY => FALSE)); $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). @@ -1083,7 +1092,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, CURLOPT_HEADER => FALSE)); + $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post)); // Ensure that any changes to variables in the other thread are picked up. $this->refreshVariables(); @@ -1137,7 +1146,7 @@ protected function checkForMetaRefresh() { */ protected function drupalHead($path, Array $options = array()) { $options['absolute'] = TRUE; - $out = $this->curlExec(array(CURLOPT_HEADER => TRUE, CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options))); + $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options))); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. return $out; } @@ -1420,6 +1429,92 @@ protected function getUrl() { return $this->url; } + /** + * Gets the HTTP response headers of the requested page. Normally we are only + * interested in the headers returned by the last request. However, if a page + * is redirected or HTTP authentication is in use, multiple requests will be + * required to retrieve the page. Headers from all requests may be requested + * by passing TRUE to this function. + * + * @param $all_requests + * Boolean value specifying whether to return headers from all requests + * instead of just the last request. Defaults to FALSE. + * @return + * A name/value array if headers from only the last request are requested. + * If headers from all requests are requested, an array of name/value + * arrays, one for each request. + * + * The pseudonym ":status" is used for the HTTP status line. + * + * Values for duplicate headers are stored as a single comma-separated list. + */ + protected function drupalGetHeaders($all_requests = FALSE) { + $request = 0; + $headers = array($request => array()); + foreach ($this->headers as $header) { + $header = trim($header); + if ($header === '') { + $request++; + } + else { + if (strpos($header, 'HTTP/') === 0) { + $name = ':status'; + $value = $header; + } + else { + list($name, $value) = explode(':', $header, 2); + $name = strtolower($name); + } + if (isset($headers[$request][$name])) { + $headers[$request][$name] .= ',' . trim($value); + } + else { + $headers[$request][$name] = trim($value); + } + } + } + if (!$all_requests) { + $headers = array_pop($headers); + } + return $headers; + } + + /** + * Gets the value of an HTTP response header. If multiple requests were + * required to retrieve the page, only the headers from the last request will + * be checked by default. However, if TRUE is passed as the second argument, + * all requests will be processed from last to first until the header is + * found. + * + * @param $name + * The name of the header to retrieve. Names are case-insensitive (see RFC + * 2616 section 4.2). + * @param $all_requests + * Boolean value specifying whether to check all requests if the header is + * not found in the last request. Defaults to FALSE. + * @return + * The HTTP header value or FALSE if not found. + */ + protected function drupalGetHeader($name, $all_requests = FALSE) { + $name = strtolower($name); + $header = FALSE; + if ($all_requests) { + foreach (array_reverse($this->drupalGetHeaders(TRUE)) as $headers) { + if (isset($headers[$name])) { + $header = $headers[$name]; + break; + } + } + } + else { + $headers = $this->drupalGetHeaders(); + if (isset($headers[$name])) { + $header = $headers[$name]; + } + } + return $header; + } + /** * Gets the current raw HTML of requested page. */ diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index ce1cb2a988a00be00d5011b311d4f6b8b9e1a273..0aa8301159c4deb8efc38e9271c363cc3e69386c 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -44,6 +44,7 @@ class SimpleTestTestCase extends DrupalWebTestCase { global $conf; if (!$this->inCURL()) { $this->drupalGet('node'); + $this->assertTrue($this->drupalGetHeader('Date'), t('An HTTP header was received.')); $this->assertTitle(variable_get('site_name', 'Drupal'), t('Site title matches.')); // Make sure that we are locked out of the installer when prefixing // using the user-agent header. This is an important security check. @@ -51,6 +52,15 @@ class SimpleTestTestCase extends DrupalWebTestCase { $this->drupalGet($base_url . '/install.php', array('external' => TRUE)); $this->assertResponse(403, 'Cannot access install.php with a "simpletest" user-agent header.'); + + $this->drupalLogin($this->drupalCreateUser()); + $headers = $this->drupalGetHeaders(TRUE); + $this->assertEqual(count($headers), 2, t('There was one intermediate request.')); + $this->assertTrue(strpos($headers[0][':status'], '302') !== FALSE, t('Intermediate response code was 302.')); + $this->assertFalse(empty($headers[0]['location']), t('Intermediate request contained a Location header.')); + $this->assertEqual($this->getUrl(), $headers[0]['location'], t('HTTP redirect was followed')); + $this->assertFalse($this->drupalGetHeader('Location'), t('Headers from intermediate request were reset.')); + $this->assertResponse(200, t('Response code from intermediate request was reset.')); } } diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test index f3fbb1620fd9f4c0a4c63b715360cafb6d10d6cd..ec5a598d8665f814715e13fde008f036a105bbdb 100644 --- a/modules/simpletest/tests/bootstrap.test +++ b/modules/simpletest/tests/bootstrap.test @@ -104,7 +104,7 @@ class BootstrapPageCacheTestCase extends DrupalWebTestCase { $this->drupalGet($base_url); $this->drupalHead($base_url); - $this->assertText('ETag: ', t('Verify presence of ETag header indicating that page caching is enabled.')); + $this->assertTrue($this->drupalGetHeader('ETag') !== FALSE, t('Verify presence of ETag header indicating that page caching is enabled.')); } } diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test index 082345e64b15d0a2eefc094647992ff72111523e..85a1bb80da09b8ed10b433b3a153224b75d8cdb6 100644 --- a/modules/simpletest/tests/session.test +++ b/modules/simpletest/tests/session.test @@ -7,8 +7,6 @@ */ class SessionTestCase extends DrupalWebTestCase { - protected $saved_cookie; - function getInfo() { return array( 'name' => t('Session tests'), @@ -21,18 +19,6 @@ class SessionTestCase extends DrupalWebTestCase { parent::setUp('session_test'); } - /** - * Implementation of curlHeaderCallback(). - */ - protected function curlHeaderCallback($ch, $header) { - // Look for a Set-Cookie header. - if (preg_match('/^Set-Cookie.+$/i', $header, $matches)) { - $this->saved_cookie = $header; - } - - return parent::curlHeaderCallback($ch, $header); - } - /** * Tests for drupal_save_session() and drupal_session_regenerate(). */ @@ -49,7 +35,7 @@ class SessionTestCase extends DrupalWebTestCase { $this->sessionReset($user->uid); // Make sure the session cookie is set as HttpOnly. $this->drupalLogin($user); - $this->assertTrue(preg_match('/HttpOnly/i', $this->saved_cookie), t('Session cookie is set as HttpOnly.')); + $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), t('Session cookie is set as HttpOnly.')); $this->drupalLogout(); // Verify that the session is regenerated if a module calls exit // in hook_user_login().