Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1154
diff -u -9 -p -r1.1154 common.inc
--- includes/common.inc	29 Apr 2010 05:33:43 -0000	1.1154
+++ includes/common.inc	30 Apr 2010 08:44:24 -0000
@@ -767,113 +767,125 @@ function drupal_http_request($url, array
 
   $result = new stdClass();
 
   // Parse the URL and make sure we can handle the schema.
   $uri = @parse_url($url);
 
   if ($uri == FALSE) {
     $result->error = 'unable to parse URL';
     $result->code = -1001;
+    module_invoke_all('http_response', $result, $uri, $options);
     return $result;
   }
 
   if (!isset($uri['scheme'])) {
     $result->error = 'missing schema';
     $result->code = -1002;
+    module_invoke_all('http_response', $result, $uri, $options);
     return $result;
   }
 
   timer_start(__FUNCTION__);
 
   // Merge the default options.
   $options += array(
     'headers' => array(),
     'method' => 'GET',
     'data' => NULL,
     'max_redirects' => 3,
     'timeout' => 30,
   );
 
+  // Merge the default headers.
+  $options['headers'] += array(
+    'User-Agent' => 'Drupal (+http://drupal.org/)',
+  );
+
+  // If the server URL has a user then attempt to use basic authentication.
+  if (isset($uri['user'])) {
+    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
+  }
+
+  // If the database prefix is being used by SimpleTest to run the tests in a copied
+  // database then set the user-agent header to the database prefix so that any
+  // calls to other Drupal pages will run the SimpleTest prefixed database. The
+  // user-agent is used to ensure that multiple testing sessions running at the
+  // 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 (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
+    $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
+  }
+
+  // Allow modules to modify the request or even return a cached or generated
+  // response.
+  drupal_alter('http_request', $uri, $options, $result);
+  if (!empty($result->code)) {
+    module_invoke_all('http_response', $result, $uri, $options);
+    return $result;
+  }
+
+  // Only add Content-Length if we actually have any content or if it is a POST
+  // or PUT request. Some non-standard servers get confused by Content-Length in
+  // at least HEAD/GET requests, and Squid always requires Content-Length in
+  // POST/PUT requests.
+  $content_length = strlen($options['data']);
+  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
+    $options['headers']['Content-Length'] = $content_length;
+  }
+
   switch ($uri['scheme']) {
     case 'http':
     case 'feed':
       $port = isset($uri['port']) ? $uri['port'] : 80;
       $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
       $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
       break;
     case 'https':
       // Note: Only works when PHP is compiled with OpenSSL support.
       $port = isset($uri['port']) ? $uri['port'] : 443;
-      $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
       $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
       break;
     default:
       $result->error = 'invalid schema ' . $uri['scheme'];
       $result->code = -1003;
+      module_invoke_all('http_response', $result, $uri, $options);
       return $result;
   }
 
+  // RFC 2616: "non-standard ports MUST, default ports MAY be included".
+  // We don't add the standard port to prevent from breaking rewrite rules
+  // checking the host that do not take into account the port number.
+  $options['headers']['Host'] = $host;
+
   // Make sure the socket opened properly.
   if (!$fp) {
     // When a network error occurs, we use a negative number so it does not
     // clash with the HTTP status codes.
     $result->code = -$errno;
     $result->error = trim($errstr);
 
     // Mark that this request failed. This will trigger a check of the web
     // server's ability to make outgoing HTTP requests the next time that
     // requirements checking is performed.
     // See system_requirements()
     variable_set('drupal_http_request_fails', TRUE);
 
+    module_invoke_all('http_response', $result, $uri, $options);
+
     return $result;
   }
 
   // Construct the path to act on.
   $path = isset($uri['path']) ? $uri['path'] : '/';
   if (isset($uri['query'])) {
     $path .= '?' . $uri['query'];
   }
 
-  // Merge the default headers.
-  $options['headers'] += array(
-    'User-Agent' => 'Drupal (+http://drupal.org/)',
-  );
-
-  // RFC 2616: "non-standard ports MUST, default ports MAY be included".
-  // We don't add the standard port to prevent from breaking rewrite rules
-  // checking the host that do not take into account the port number.
-  $options['headers']['Host'] = $host;
-
-  // Only add Content-Length if we actually have any content or if it is a POST
-  // or PUT request. Some non-standard servers get confused by Content-Length in
-  // at least HEAD/GET requests, and Squid always requires Content-Length in
-  // POST/PUT requests.
-  $content_length = strlen($options['data']);
-  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
-    $options['headers']['Content-Length'] = $content_length;
-  }
-
-  // If the server URL has a user then attempt to use basic authentication.
-  if (isset($uri['user'])) {
-    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
-  }
-
-  // If the database prefix is being used by SimpleTest to run the tests in a copied
-  // database then set the user-agent header to the database prefix so that any
-  // calls to other Drupal pages will run the SimpleTest prefixed database. The
-  // user-agent is used to ensure that multiple testing sessions running at the
-  // 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 (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
-    $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
-  }
-
   $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
   foreach ($options['headers'] as $name => $value) {
     $request .= $name . ': ' . trim($value) . "\r\n";
   }
   $request .= "\r\n" . $options['data'];
   $result->request = $request;
 
   fwrite($fp, $request);
 
@@ -896,18 +908,19 @@ function drupal_http_request($url, array
     $response .= $chunk;
     $info = stream_get_meta_data($fp);
     $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
   }
   fclose($fp);
 
   if ($info['timed_out']) {
     $result->code = HTTP_REQUEST_TIMEOUT;
     $result->error = 'request timed out';
+    module_invoke_all('http_response', $result, $uri, $options);
     return $result;
   }
   // Parse response headers from the response body.
   list($response, $result->data) = explode("\r\n\r\n", $response, 2);
   $response = preg_split("/\r\n|\n|\r/", $response);
 
   // Parse the response status line.
   list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
   $result->protocol = $protocol;
@@ -996,18 +1009,20 @@ function drupal_http_request($url, array
         $result = drupal_http_request($location, $options);
         $result->redirect_code = $code;
       }
       $result->redirect_url = $location;
       break;
     default:
       $result->error = $status_message;
   }
 
+  module_invoke_all('http_response', $result, $uri, $options);
+
   return $result;
 }
 /**
  * @} End of "HTTP handling".
  */
 
 function _fix_gpc_magic(&$item) {
   if (is_array($item)) {
     array_walk($item, '_fix_gpc_magic');
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.162
diff -u -9 -p -r1.162 system.api.php
--- modules/system/system.api.php	29 Apr 2010 04:57:59 -0000	1.162
+++ modules/system/system.api.php	30 Apr 2010 08:44:25 -0000
@@ -3522,18 +3522,59 @@ function hook_system_themes_page_alter(&
   foreach ($theme_groups as $state => &$group) {
     foreach($theme_groups[$state] as &$theme) {
       // Add a foo link to each list of theme operations.
       $theme->operations[] = l(t('Foo'), 'admin/appearance/foo', array('query' => array('theme' => $theme->name)));
     }
   }
 }
 
 /**
+ * Allows modules to alter HTTP requests made with drupal_http_request().
+ *
+ * This allows modules to adjust options, add HTTP headers, change the URL, or
+ * even return complete responses that are returned without sending the actual
+ * request.
+ *
+ * @param $uri
+ *   A URL array as returned by parse_url().
+ * @param $options
+ *   The options array passed to drupal_http_request().
+ * @param $result
+ *   The result object. By default, this is an empty class. If a module sets
+ *   $result->code, the networking code in drupal_http_request() is bypassed,
+ *   and the result is returned to the caller.
+ */
+function hook_http_request_alter(&$uri, &$options, &$result) {
+  // We talk a lot to this host, but it is unstable, so do not wait too long.
+  if ($uri['host'] == 'example.com') {
+    $options['timeout'] = 3;
+  }
+}
+
+/**
+ * Allows modules to act upon a completed HTTP request.
+ *
+ * @param $result
+ *   The result that is about to be returned from drupal_http_request(). Modules
+ *   may alter this.
+ * @param $uri
+ *   A URL array as returned by parse_url().
+ * @param $options
+ *   The options array passed to drupal_http_request().
+ */
+function hook_http_response(&$result, $uri, $options) {
+  // Our own XML-RPC server is unstable - log all timeouts.
+  if ($uri['host'] == 'xmlrpc.example.com' && $result->code == HTTP_REQUEST_TIMEOUT) {
+    watchdog('xmlrpc', 'Could not connect to xmlrpc.example.com.', array(), WATCHDOG_WARNING);
+  }
+}
+
+/**
  * Alters inbound URL requests.
  *
  * @param $path
  *   The path being constructed, which, if a path alias, has been resolved to a
  *   Drupal path by the database, and which also may have been altered by other
  *   modules before this one.
  * @param $original_path
  *   The original path, before being checked for path aliases or altered by any
  *   modules.
