diff --git a/core/includes/common.inc b/core/includes/common.inc index 35056de..4acbdff 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -764,336 +764,6 @@ function _external_url_is_local($url) { } /** - * Performs an HTTP request. - * - * This is a flexible and powerful HTTP client implementation. Correctly - * handles GET, POST, PUT or any other HTTP requests. Handles redirects. - * - * @param $url - * A string containing a fully qualified URI. - * @param array $options - * (optional) An array that can have one or more of the following elements: - * - headers: An array containing request headers to send as name/value pairs. - * - method: A string containing the request method. Defaults to 'GET'. - * - data: A string containing the request body, formatted as - * 'param=value¶m=value&...'. Defaults to NULL. - * - max_redirects: An integer representing how many times a redirect - * may be followed. Defaults to 3. - * - timeout: A float representing the maximum number of seconds the function - * call may take. The default is 30 seconds. If a timeout occurs, the error - * code is set to the HTTP_REQUEST_TIMEOUT constant. - * - context: A context resource created with stream_context_create(). - * - * @return object - * An object that can have one or more of the following components: - * - request: A string containing the request body that was sent. - * - code: An integer containing the response status code, or the error code - * if an error occurred. - * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0). - * - status_message: The status message from the response, if a response was - * received. - * - redirect_code: If redirected, an integer containing the initial response - * status code. - * - redirect_url: If redirected, a string containing the URL of the redirect - * target. - * - error: If an error occurred, the error message. Otherwise not set. - * - headers: An array containing the response headers as name/value pairs. - * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for - * easy access the array keys are returned in lower case. - * - data: A string containing the response body that was received. - */ -function drupal_http_request($url, array $options = 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; - return $result; - } - - if (!isset($uri['scheme'])) { - $result->error = 'missing schema'; - $result->code = -1002; - return $result; - } - - timer_start(__FUNCTION__); - - // Merge the default options. - $options += array( - 'headers' => array(), - 'method' => 'GET', - 'data' => NULL, - 'max_redirects' => 3, - 'timeout' => 30.0, - 'context' => NULL, - ); - - // Merge the default headers. - $options['headers'] += array( - 'User-Agent' => 'Drupal (+http://drupal.org/)', - ); - - // stream_socket_client() requires timeout to be a float. - $options['timeout'] = (float) $options['timeout']; - - // Use a proxy if one is defined and the host is not on the excluded list. - $proxy_server = settings()->get('proxy_server', ''); - if ($proxy_server && _drupal_http_use_proxy($uri['host'])) { - // Set the scheme so we open a socket to the proxy server. - $uri['scheme'] = 'proxy'; - // Set the path to be the full URL. - $uri['path'] = $url; - // Since the URL is passed as the path, we won't use the parsed query. - unset($uri['query']); - - // Add in username and password to Proxy-Authorization header if needed. - if ($proxy_username = settings()->get('proxy_username', '')) { - $proxy_password = settings()->get('proxy_password', ''); - $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . (!empty($proxy_password) ? ":" . $proxy_password : '')); - } - // Some proxies reject requests with any User-Agent headers, while others - // require a specific one. - $proxy_user_agent = settings()->get('proxy_user_agent', ''); - // The default value matches neither condition. - if ($proxy_user_agent === NULL) { - unset($options['headers']['User-Agent']); - } - elseif ($proxy_user_agent) { - $options['headers']['User-Agent'] = $proxy_user_agent; - } - } - - switch ($uri['scheme']) { - case 'proxy': - // Make the socket connection to a proxy server. - $socket = 'tcp://' . $proxy_server . ':' . settings()->get('proxy_port', 8080); - // The Host header still needs to match the real request. - $options['headers']['Host'] = $uri['host']; - $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : ''; - break; - - case 'http': - case 'feed': - $port = isset($uri['port']) ? $uri['port'] : 80; - $socket = 'tcp://' . $uri['host'] . ':' . $port; - // 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'] = $uri['host'] . ($port != 80 ? ':' . $port : ''); - break; - - case 'https': - // Note: Only works when PHP is compiled with OpenSSL support. - $port = isset($uri['port']) ? $uri['port'] : 443; - $socket = 'ssl://' . $uri['host'] . ':' . $port; - $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : ''); - break; - - default: - $result->error = 'invalid schema ' . $uri['scheme']; - $result->code = -1003; - return $result; - } - - if (empty($options['context'])) { - $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']); - } - else { - // Create a stream with context. Allows verification of a SSL certificate. - $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']); - } - - // 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) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket)); - return $result; - } - - // Construct the path to act on. - $path = isset($uri['path']) ? $uri['path'] : '/'; - if (isset($uri['query'])) { - $path .= '?' . $uri['query']; - } - - // 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'] . (isset($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. - $test_info = &$GLOBALS['drupal_test_info']; - if (!empty($test_info['test_run_id'])) { - $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']); - } - - $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; - // Calculate how much time is left of the original timeout value. - $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000; - if ($timeout > 0) { - stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1))); - fwrite($fp, $request); - } - - // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782 - // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but - // instead must invoke stream_get_meta_data() each iteration. - $info = stream_get_meta_data($fp); - $alive = !$info['eof'] && !$info['timed_out']; - $response = ''; - - while ($alive) { - // Calculate how much time is left of the original timeout value. - $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000; - if ($timeout <= 0) { - $info['timed_out'] = TRUE; - break; - } - stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1))); - $chunk = fread($fp, 1024); - $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'; - return $result; - } - // Parse response headers from the response body. - // Be tolerant of malformed HTTP responses that separate header and body with - // \n\n or \r\r instead of \r\n\r\n. - list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $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; - $result->status_message = $status_message; - - $result->headers = array(); - - // Parse the response headers. - while ($line = trim(array_shift($response))) { - list($name, $value) = explode(':', $line, 2); - $name = strtolower($name); - if (isset($result->headers[$name]) && $name == 'set-cookie') { - // RFC 2109: the Set-Cookie response header comprises the token Set- - // Cookie:, followed by a comma-separated list of one or more cookies. - $result->headers[$name] .= ',' . trim($value); - } - else { - $result->headers[$name] = trim($value); - } - } - - $responses = array( - 100 => 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Time-out', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Large', - 415 => 'Unsupported Media Type', - 416 => 'Requested range not satisfiable', - 417 => 'Expectation Failed', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Time-out', - 505 => 'HTTP Version not supported', - ); - // RFC 2616 states that all unknown HTTP codes must be treated the same as the - // base code in their class. - if (!isset($responses[$code])) { - $code = floor($code / 100) * 100; - } - $result->code = $code; - - switch ($code) { - case 200: // OK - case 304: // Not modified - break; - case 301: // Moved permanently - case 302: // Moved temporarily - case 307: // Moved temporarily - $location = $result->headers['location']; - $options['timeout'] -= timer_read(__FUNCTION__) / 1000; - if ($options['timeout'] <= 0) { - $result->code = HTTP_REQUEST_TIMEOUT; - $result->error = 'request timed out'; - } - elseif ($options['max_redirects']) { - // Redirect to the new location. - $options['max_redirects']--; - $result = drupal_http_request($location, $options); - $result->redirect_code = $code; - } - if (!isset($result->redirect_url)) { - $result->redirect_url = $location; - } - break; - default: - $result->error = $status_message; - } - - return $result; -} - -/** * Helper function for determining hosts excluded from needing a proxy. * * @return diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php deleted file mode 100644 index b2670fc..0000000 --- a/core/modules/system/lib/Drupal/system/Tests/Common/HttpRequestTest.php +++ /dev/null @@ -1,160 +0,0 @@ - 'Drupal HTTP request', - 'description' => "Performs tests on Drupal's HTTP request mechanism.", - 'group' => 'Common', - ); - } - - /** - * Checks HTTP requests. - */ - function testDrupalHTTPRequest() { - global $is_https; - - // Parse URL schema. - $missing_scheme = drupal_http_request('example.com/path'); - $this->assertEqual($missing_scheme->code, -1002, 'Returned with "-1002" error code.'); - $this->assertEqual($missing_scheme->error, 'missing schema', 'Returned with "missing schema" error message.'); - - $unable_to_parse = drupal_http_request('http:///path'); - $this->assertEqual($unable_to_parse->code, -1001, 'Returned with "-1001" error code.'); - $this->assertEqual($unable_to_parse->error, 'unable to parse URL', 'Returned with "unable to parse URL" error message.'); - - // Fetch the test page. - $result = drupal_http_request(url('test-page', array('absolute' => TRUE))); - $this->assertEqual($result->code, 200, 'Fetched page successfully.'); - $this->drupalSetContent($result->data); - $this->assertTitle(t('Test page | @site-name', array('@site-name' => config('system.site')->get('name')))); - - // Test that Drupal.settings is properly parsed. - $settings = $this->drupalGetSettings(); - $this->assertIdentical($settings['test-setting'], 'azAZ09();.,\\\/-_{}'); - - // Test that code and status message is returned. - $result = drupal_http_request(url('pagedoesnotexist', array('absolute' => TRUE))); - $this->assertTrue(!empty($result->protocol), 'Result protocol is returned.'); - $this->assertEqual($result->code, '404', 'Result code is 404'); - $this->assertEqual($result->status_message, 'Not Found', 'Result status message is "Not Found"'); - - // Skip the timeout tests when the testing environment is HTTPS because - // stream_set_timeout() does not work for SSL connections. - // @link http://bugs.php.net/bug.php?id=47929 - if (!$is_https) { - // Test that timeout is respected. The test machine is expected to be able - // to make the connection (i.e. complete the fsockopen()) in 2 seconds and - // return within a total of 5 seconds. If the test machine is extremely - // slow, the test will fail. fsockopen() has been seen to time out in - // slightly less than the specified timeout, so allow a little slack on - // the minimum expected time (i.e. 1.8 instead of 2). - timer_start(__METHOD__); - $result = drupal_http_request(url('system-test/sleep/10', array('absolute' => TRUE)), array('timeout' => 2)); - $time = timer_read(__METHOD__) / 1000; - $this->assertTrue(1.8 < $time && $time < 5, format_string('Request timed out (%time seconds).', array('%time' => $time))); - $this->assertTrue($result->error, 'An error message was returned.'); - $this->assertEqual($result->code, HTTP_REQUEST_TIMEOUT, 'Proper error code was returned.'); - } - } - - /** - * Tests HTTP basic authorization. - */ - function testDrupalHTTPRequestBasicAuth() { - $username = $this->randomName(); - $password = $this->randomName(); - $url = url('system-test/auth', array('absolute' => TRUE)); - - $auth = str_replace('://', '://' . $username . ':' . $password . '@', $url); - $result = drupal_http_request($auth); - - $this->drupalSetContent($result->data); - $this->assertRaw($username, '$_SERVER["PHP_AUTH_USER"] is passed correctly.'); - $this->assertRaw($password, '$_SERVER["PHP_AUTH_PW"] is passed correctly.'); - } - - /** - * Tests HTTP redirect requests. - */ - function testDrupalHTTPRequestRedirect() { - $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($redirect_301->redirect_code, 301, 'drupal_http_request follows the 301 redirect.'); - - $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 0)); - $this->assertFalse(isset($redirect_301->redirect_code), 'drupal_http_request does not follow 301 redirect if max_redirects = 0.'); - - $redirect_invalid = drupal_http_request(url('system-test/redirect-noscheme', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($redirect_invalid->code, -1002, format_string('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error))); - $this->assertEqual($redirect_invalid->error, 'missing schema', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); - - $redirect_invalid = drupal_http_request(url('system-test/redirect-noparse', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($redirect_invalid->code, -1001, format_string('301 redirect to invalid URL returned with error message code "!error".', array('!error' => $redirect_invalid->error))); - $this->assertEqual($redirect_invalid->error, 'unable to parse URL', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); - - $redirect_invalid = drupal_http_request(url('system-test/redirect-invalid-scheme', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($redirect_invalid->code, -1003, format_string('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error))); - $this->assertEqual($redirect_invalid->error, 'invalid schema ftp', format_string('301 redirect to invalid URL returned with error message "!error".', array('!error' => $redirect_invalid->error))); - - $redirect_302 = drupal_http_request(url('system-test/redirect/302', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($redirect_302->redirect_code, 302, 'drupal_http_request follows the 302 redirect.'); - - $redirect_302 = drupal_http_request(url('system-test/redirect/302', array('absolute' => TRUE)), array('max_redirects' => 0)); - $this->assertFalse(isset($redirect_302->redirect_code), 'drupal_http_request does not follow 302 redirect if $retry = 0.'); - - $redirect_307 = drupal_http_request(url('system-test/redirect/307', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($redirect_307->redirect_code, 307, 'drupal_http_request follows the 307 redirect.'); - - $redirect_307 = drupal_http_request(url('system-test/redirect/307', array('absolute' => TRUE)), array('max_redirects' => 0)); - $this->assertFalse(isset($redirect_307->redirect_code), 'drupal_http_request does not follow 307 redirect if max_redirects = 0.'); - - $multiple_redirect_final_url = url('system-test/multiple-redirects/0', array('absolute' => TRUE)); - $multiple_redirect_1 = drupal_http_request(url('system-test/multiple-redirects/1', array('absolute' => TRUE)), array('max_redirects' => 1)); - $this->assertEqual($multiple_redirect_1->redirect_url, $multiple_redirect_final_url, 'redirect_url contains the final redirection location after 1 redirect.'); - - $multiple_redirect_3 = drupal_http_request(url('system-test/multiple-redirects/3', array('absolute' => TRUE)), array('max_redirects' => 3)); - $this->assertEqual($multiple_redirect_3->redirect_url, $multiple_redirect_final_url, 'redirect_url contains the final redirection location after 3 redirects.'); - } - - /** - * Tests Content-language headers generated by Drupal. - */ - function testDrupalHTTPRequestHeaders() { - // Check the default header. - $request = drupal_http_request(url('', array('absolute' => TRUE))); - $this->assertEqual($request->headers['content-language'], 'en', 'Content-Language HTTP header is English.'); - - // Add French language. - $language = new Language(array( - 'langcode' => 'fr', - 'name' => 'French', - )); - language_save($language); - - // Request front page in French and check for matching Content-language. - $request = drupal_http_request(url('', array('absolute' => TRUE, 'language' => $language))); - $this->assertEqual($request->headers['content-language'], 'fr', 'Content-Language HTTP header is French.'); - } -} diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module index 4494299..97c6f03 100644 --- a/core/modules/system/tests/modules/system_test/system_test.module +++ b/core/modules/system/tests/modules/system_test/system_test.module @@ -4,12 +4,6 @@ * Implements hook_menu(). */ function system_test_menu() { - $items['system-test/sleep/%'] = array( - 'page callback' => 'system_test_sleep', - 'page arguments' => array(2), - 'access callback' => TRUE, - 'type' => MENU_CALLBACK, - ); $items['system-test/auth'] = array( 'page callback' => 'system_test_basic_auth_page', 'access callback' => TRUE, @@ -21,41 +15,11 @@ function system_test_menu() { 'access arguments' => array('administer software updates'), 'type' => MENU_CALLBACK, ); - $items['system-test/redirect/%'] = array( - 'title' => 'Redirect', - 'page callback' => 'system_test_redirect', - 'page arguments' => array(2), - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); - $items['system-test/multiple-redirects/%'] = array( - 'title' => 'Redirect', - 'page callback' => 'system_test_multiple_redirects', - 'page arguments' => array(2), - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); $items['system-test/set-header'] = array( 'page callback' => 'system_test_set_header', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - $items['system-test/redirect-noscheme'] = array( - 'page callback' => 'system_test_redirect_noscheme', - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); - $items['system-test/redirect-noparse'] = array( - 'page callback' => 'system_test_redirect_noparse', - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); - $items['system-test/redirect-invalid-scheme'] = array( - 'page callback' => 'system_test_redirect_invalid_scheme', - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - ); - $items['system-test/variable-get'] = array( 'title' => 'Variable Get', 'page callback' => 'variable_get', @@ -109,70 +73,17 @@ function system_test_menu() { return $items; } -function system_test_sleep($seconds) { - sleep($seconds); -} - function system_test_basic_auth_page() { $output = t('$_SERVER[\'PHP_AUTH_USER\'] is @username.', array('@username' => $_SERVER['PHP_AUTH_USER'])); $output .= t('$_SERVER[\'PHP_AUTH_PW\'] is @password.', array('@password' => $_SERVER['PHP_AUTH_PW'])); return $output; } -function system_test_redirect($code) { - $code = (int) $code; - if ($code != 200) { - // Header names are case-insensitive. - header("locaTION: " . url('system-test/redirect/200', array('absolute' => TRUE)), TRUE, $code); - exit; - } - return ''; -} - -/** - * Menu callback; sends a redirect header to itself until $count argument is 0. - * - * Emulates the variable number of redirects (given by initial $count argument) - * to the final destination URL by continuous sending of 301 HTTP redirect - * headers to itself together with decrementing the $count parameter until the - * $count parameter reaches 0. After that it returns an empty string to render - * the final destination page. - * - * @param $count - * The count of redirects left until the final destination page. - * - * @returns - * The location redirect if the $count > 0, otherwise an empty string. - */ -function system_test_multiple_redirects($count) { - $count = (int) $count; - if ($count > 0) { - header("location: " . url('system-test/multiple-redirects/' . --$count, array('absolute' => TRUE)), TRUE, 301); - exit; - } - return ''; -} - function system_test_set_header() { drupal_add_http_header($_GET['name'], $_GET['value']); return t('The following header was set: %name: %value', array('%name' => $_GET['name'], '%value' => $_GET['value'])); } -function system_test_redirect_noscheme() { - header("Location: localhost/path", TRUE, 301); - exit; -} - -function system_test_redirect_noparse() { - header("Location: http:///path", TRUE, 301); - exit; -} - -function system_test_redirect_invalid_scheme() { - header("Location: ftp://localhost/path", TRUE, 301); - exit; -} - /** * Implements hook_modules_installed(). */