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.