Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.803
diff -u -F^f -r1.803 common.inc
--- includes/common.inc	11 Oct 2008 02:32:32 -0000	1.803
+++ includes/common.inc	11 Oct 2008 17:11:46 -0000
@@ -398,22 +398,36 @@ function drupal_access_denied() {
  *
  * @param $url
  *   A string containing a fully qualified URI.
- * @param $headers
- *   An array containing an HTTP header => value pair.
- * @param $method
- *   A string defining the HTTP request to use.
- * @param $data
- *   A string containing data to include in the request.
- * @param $retry
- *   An integer representing how many times to retry the request in case of a
- *   redirect.
+ * @param $options
+ *   An associative array of options, with the following keys:
+ *   - 'headers'
+ *       An array containing an HTTP header => value pair.
+ *   - 'method' (default 'GET')
+ *       A string defining the HTTP request to use.
+ *   - 'data'
+ *       A string containing data to include in the request.
+ *   - 'retry' (default 3)
+ *       An integer representing how many times to retry the request
+ *       in case of a redirect.
+ *   - 'timeout' (default 20)
+ *       The number of seconds before the timeout of the request.
  * @return
  *   An object containing the HTTP request headers, response code, headers,
  *   data and redirect status.
  */
-function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
+function drupal_http_request($url, $options = array()) {
   global $db_prefix;
   static $self_test = FALSE;
+
+  // Merge in defaults.
+  $options += array(
+    'headers' => array(),
+    'method' => 'GET',
+    'data' => NULL,
+    'retry' => 3,
+    'timeout' => 20,
+  );
+
   $result = new stdClass();
   // Try to clear the drupal_http_request_fails variable if it's set. We
   // can't tie this call to any error because there is no surefire way to
@@ -448,13 +462,13 @@ function drupal_http_request($url, $head
     case 'http':
       $port = isset($uri['port']) ? $uri['port'] : 80;
       $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
-      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
+      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
       break;
     case 'https':
       // Note: Only works for PHP 4.3 compiled with OpenSSL.
       $port = isset($uri['port']) ? $uri['port'] : 443;
       $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
-      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, 20);
+      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
       break;
     default:
       $result->error = 'invalid schema ' . $uri['scheme'];
@@ -483,7 +497,7 @@ function drupal_http_request($url, $head
     // host that do not take into account the port number.
     'Host' => "Host: $host",
     'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
-    'Content-Length' => 'Content-Length: ' . strlen($data)
+    'Content-Length' => 'Content-Length: ' . strlen($options['data'])
   );
 
   // If the server url has a user then attempt to use basic authentication
@@ -498,21 +512,24 @@ function drupal_http_request($url, $head
   // same time won't interfere with each other as they would if the database
   // prefix were stored statically in a file or database variable.
   if (preg_match("/^simpletest\d+/", $db_prefix)) {
-    $headers['User-Agent'] = $db_prefix;
+    $options['headers']['User-Agent'] = $db_prefix;
   }
 
-  foreach ($headers as $header => $value) {
+  foreach ($options['headers'] as $header => $value) {
     $defaults[$header] = $header . ': ' . $value;
   }
 
-  $request = $method . ' ' . $path . " HTTP/1.0\r\n";
+  $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
   $request .= implode("\r\n", $defaults);
   $request .= "\r\n\r\n";
-  if ($data) {
-    $request .= $data . "\r\n";
+  if ($options['data']) {
+    $request .= $options['data'] . "\r\n";
   }
   $result->request = $request;
 
+  // Set a timeout on the stream
+  stream_set_timeout($fp, $options['timeout']);
+
   fwrite($fp, $request);
 
   // Fetch response.
@@ -520,6 +537,14 @@ function drupal_http_request($url, $head
   while (!feof($fp) && $chunk = fread($fp, 1024)) {
     $response .= $chunk;
   }
+
+  // Determine whether the response timed out during read
+  $response_metadata = stream_get_meta_data($fp);
+  if (array_key_exists('timed_out', $response_metadata) && $response_metadata['timed_out']) {
+    $result->error = 'Connection timed out while reading response';
+    return $result;
+  }
+
   fclose($fp);
 
   // Parse response.
@@ -564,9 +589,9 @@ function drupal_http_request($url, $head
     case 307: // Moved temporarily
       $location = $result->headers['Location'];
 
-      if ($retry) {
-        $result = drupal_http_request($location, $headers, $method, $data, --$retry);
-        $result->redirect_code = $code;
+      if ($options['retry']) {
+        $options['retry']--;
+        $result = drupal_http_request($result->headers['Location'], $options);
       }
       $result->redirect_url = $location;
 
@@ -2704,7 +2729,7 @@ function drupal_system_listing($mask, $d
 
 /**
  * Hands off structured Drupal arrays to type-specific *_alter implementations.
- * 
+ *
  * This dispatch function hands off structured Drupal arrays to type-specific
  * *_alter implementations. It ensures a consistent interface for all altering
  * operations.
Index: includes/xmlrpc.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/xmlrpc.inc,v
retrieving revision 1.53
diff -u -F^f -r1.53 xmlrpc.inc
--- includes/xmlrpc.inc	31 Aug 2008 12:50:45 -0000	1.53
+++ includes/xmlrpc.inc	11 Oct 2008 17:11:47 -0000
@@ -439,7 +439,7 @@ function _xmlrpc() {
     $method = array_shift($args);
   }
   $xmlrpc_request = xmlrpc_request($method, $args);
-  $result = drupal_http_request($url, array("Content-Type" => "text/xml"), 'POST', $xmlrpc_request->xml);
+  $result = drupal_http_request($url, array('headers' => array("Content-Type" => "text/xml"), 'method' => 'POST', 'data' => $xmlrpc_request->xml));
   if ($result->code != 200) {
     xmlrpc_error($result->code, $result->error);
     return FALSE;
Index: modules/aggregator/aggregator.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.module,v
retrieving revision 1.394
diff -u -F^f -r1.394 aggregator.module
--- modules/aggregator/aggregator.module	9 Oct 2008 15:15:49 -0000	1.394
+++ modules/aggregator/aggregator.module	11 Oct 2008 17:11:47 -0000
@@ -593,7 +593,7 @@ function aggregator_refresh($feed) {
   }
 
   // Request feed.
-  $result = drupal_http_request($feed['url'], $headers);
+  $result = drupal_http_request($feed['url'], array('headers' => $headers));
 
   // Process HTTP response code.
   switch ($result->code) {
Index: modules/openid/openid.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/openid.module,v
retrieving revision 1.30
diff -u -F^f -r1.30 openid.module
--- modules/openid/openid.module	6 Oct 2008 11:30:11 -0000	1.30
+++ modules/openid/openid.module	11 Oct 2008 17:11:48 -0000
@@ -272,7 +272,7 @@ function openid_discovery($claimed_id) {
   if ($url['scheme'] == 'http' || $url['scheme'] == 'https') {
     // For regular URLs, try Yadis resolution first, then HTML-based discovery
     $headers = array('Accept' => 'application/xrds+xml');
-    $result = drupal_http_request($xrds_url, $headers);
+    $result = drupal_http_request($xrds_url, array('headers' => $headers));
 
     if (!isset($result->error)) {
       if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) {
@@ -290,7 +290,7 @@ function openid_discovery($claimed_id) {
         }
         if (!empty($xrds_url)) {
           $headers = array('Accept' => 'application/xrds+xml');
-          $xrds_result = drupal_http_request($xrds_url, $headers);
+          $xrds_result = drupal_http_request($xrds_url, array('headers' => $headers));
           if (!isset($xrds_result->error)) {
             $services = xrds_parse($xrds_result->data);
           }
@@ -348,7 +348,7 @@ function openid_association($op_endpoint
     $assoc_request = openid_association_request($public);
     $assoc_message = _openid_encode_message(_openid_create_message($assoc_request));
     $assoc_headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
-    $assoc_result = drupal_http_request($op_endpoint, $assoc_headers, 'POST', $assoc_message);
+    $assoc_result = drupal_http_request($op_endpoint, array('headers' => $assoc_headers, 'method' => 'POST', 'data' => $assoc_message));
     if (isset($assoc_result->error)) {
       module_invoke('system', 'check_http_request');
       return FALSE;
@@ -510,7 +510,7 @@ function openid_verify_assertion($op_end
     $request['openid.mode'] = 'check_authentication';
     $message = _openid_create_message($request);
     $headers = array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8');
-    $result = drupal_http_request($op_endpoint, $headers, 'POST', _openid_encode_message($message));
+    $result = drupal_http_request($op_endpoint, array('headers' => $headers, 'method' => 'POST', 'data' => _openid_encode_message($message)));
     if (!isset($result->error)) {
       $response = _openid_parse_message($result->data);
       if (strtolower(trim($response['is_valid'])) == 'true') {
