Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.64
diff -u -p -r1.64 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	30 Nov 2008 07:04:45 -0000	1.64
+++ modules/simpletest/drupal_web_test_case.php	2 Dec 2008 15:18:24 -0000
@@ -28,6 +28,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.
    *
    * @var string
@@ -923,6 +930,7 @@ class DrupalWebTestCase {
     $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 @@ class DrupalWebTestCase {
    *   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 @@ class DrupalWebTestCase {
     // 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 @@ class DrupalWebTestCase {
             }
             $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 @@ class DrupalWebTestCase {
    */
   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;
   }
@@ -1421,6 +1430,92 @@ class DrupalWebTestCase {
   }
 
   /**
+   * 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.
    */
   protected function drupalGetContent() {
Index: modules/simpletest/simpletest.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.test,v
retrieving revision 1.11
diff -u -p -r1.11 simpletest.test
--- modules/simpletest/simpletest.test	26 Nov 2008 13:48:49 -0000	1.11
+++ modules/simpletest/simpletest.test	2 Dec 2008 15:18:24 -0000
@@ -44,6 +44,7 @@ class SimpleTestTestCase extends DrupalW
     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 DrupalW
 
       $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.'));
     }
   }
 
Index: modules/simpletest/tests/bootstrap.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/bootstrap.test,v
retrieving revision 1.7
diff -u -p -r1.7 bootstrap.test
--- modules/simpletest/tests/bootstrap.test	23 Nov 2008 18:12:08 -0000	1.7
+++ modules/simpletest/tests/bootstrap.test	2 Dec 2008 15:18:24 -0000
@@ -104,7 +104,7 @@ class BootstrapPageCacheTestCase extends
     $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.'));
   }
 
 }
Index: modules/simpletest/tests/session.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/session.test,v
retrieving revision 1.7
diff -u -p -r1.7 session.test
--- modules/simpletest/tests/session.test	26 Nov 2008 13:48:49 -0000	1.7
+++ modules/simpletest/tests/session.test	2 Dec 2008 15:18:24 -0000
@@ -7,8 +7,6 @@
  */
 
 class SessionTestCase extends DrupalWebTestCase {
-  protected $saved_cookie;
-
   function getInfo() {
     return array(
       'name' => t('Session tests'),
@@ -22,18 +20,6 @@ class SessionTestCase extends DrupalWebT
   }
 
   /**
-   * 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().
    */
   function testSessionSaveRegenerate() {
@@ -49,7 +35,7 @@ class SessionTestCase extends DrupalWebT
     $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().
