httprl.module

  1. cis7 sites/all/modules/ulmus/httprl/httprl.module
  2. cle7 sites/all/modules/ulmus/httprl/httprl.module
  3. ecd7 sites/all/modules/ulmus/httprl/httprl.module
  4. elmsmedia7 sites/all/modules/ulmus/httprl/httprl.module
  5. harmony7 sites/all/modules/ulmus/httprl/httprl.module
  6. icor7 sites/all/modules/ulmus/httprl/httprl.module
  7. meedjum_blog7 sites/all/modules/ulmus/httprl/httprl.module
  8. mooc7 sites/all/modules/ulmus/httprl/httprl.module

HTTP Parallel Request Library module.

Functions

Namesort descending Description
httprl_acquire_lock Get a lock so background calls work.
httprl_background_processing Output text, close connection, continue processing in the background.
httprl_basic_auth Set the Authorization header if a user is set in the URI.
httprl_batch_callback Given an array of data, use mutiple processes to crunch it.
httprl_boot Implements hook_boot().
httprl_build_request_string Build the request string.
httprl_build_url_self Helper function to build an URL for asynchronous requests to self.
httprl_call_exit Performs end-of-request tasks and/or call exit directly.
httprl_cron Implements hook_cron().
httprl_decode_data Will decode chunked transfer-encoding and gzip/deflate content-encoding.
httprl_drupal_full_bootstrap Sees if Drupal has been fully booted.
httprl_drupal_get_private_key Gets the private key variable.
httprl_establish_stream_connection Use stream_socket_client() to create a connection to the server.
httprl_extract_background_callback_data Extract background callback data.
httprl_fast403 Send out a fast 403 and exit.
httprl_get_last_byte_from_range Given an array of ranges, get the last byte we need to download.
httprl_get_ranges Parse a range header into start and end byte ranges.
httprl_get_server_schema Return the server schema (http or https).
httprl_glue_url Alt to http_build_url().
httprl_handle_data If data is being sent out in this request, handle it correctly.
httprl_is_array_assoc Given an array return TRUE if all keys are numeric.
httprl_is_background_callback_capable Sees if httprl can run a background callback.
httprl_lock_release Release a lock previously acquired by lock_acquire().
httprl_lossless_assoc_array_merge Merge mutiple associative arrays into one.
httprl_menu Implements hook_menu().
httprl_multipart_encoder Multipart encode a data array.
httprl_override_core Queue and send off http request.
httprl_parse_data Extract the header and meta data from the http data stream.
httprl_parse_url Run parse_url and handle any errors.
httprl_post_processing Run post processing on the request if we are done reading.
httprl_pr Pretty print data.
httprl_print_empty If $data is bool or strlen = 0 use var_export. Recursively go deeper.
httprl_qcinp Queue Callback to run In a New Process.
httprl_queue_background_callback Run callback in the background.
httprl_reconstruct_redirects Reconstruct the internal redirect arrays.
httprl_recursive_array_reference_extract Replace data in place so pass by reference sill works.
httprl_request Queue up a HTTP request in httprl_send_request.
httprl_run_array Run multiple functions or methods independently or chained.
httprl_run_callback Run callback.
httprl_run_function Run a single function.
httprl_run_multiple Run array of data through callback.
httprl_send_request Perform many HTTP requests.
httprl_setup_proxy If server uses a proxy, change the request to utilize said proxy.
httprl_set_connection_flag Select which connect flags to use in stream_socket_client().
httprl_set_default_options Set the default options in the $options array.
httprl_set_q Sets the global $_GET['q'] parameter.
httprl_set_socket Create the TCP/SSL socket connection string.
httprl_set_user Sets the global user to the given user ID.
httprl_stream_connection_error_formatter Read the error number & string and give a nice looking error in the output.
httprl_strlen Get the length of a string in bytes.
httprl_url_inbound_alter Implements hook_url_inbound_alter().
httprl_variable_get Returns a persistent variable.
_httprl_build_drupal_root Helper function to build an URL for asynchronous requests to self.
_httprl_use_proxy Helper function for determining hosts excluded from needing a proxy.

Constants

Namesort descending Description
HTTPRL_BACKGROUND_CALLBACK Default value
HTTPRL_CONNECTION_REFUSED Error code indicating that the endpoint server has refused or dropped the connection.
HTTPRL_CONNECTION_RESET Error code indicating that the connection was forcibly closed by the remote host.
HTTPRL_CONNECT_TIMEOUT Default maximum number of seconds establishing the TCP connection of a request may take.
HTTPRL_DNS_TIMEOUT Default maximum number of seconds the DNS portion of a request may take.
HTTPRL_ERROR_INITIALIZING_STREAM An error occurred before the system connect() call. This is most likely due to a problem initializing the stream.
HTTPRL_FUNCTION_TIMEOUT Error code indicating that all requests made by httprl_send_request exceeded the specified timeout.
HTTPRL_GLOBAL_TIMEOUT Default maximum number of seconds a function call may take.
HTTPRL_HOST_NOT_FOUND Error code indicating that the host is unknown or can not be found.
HTTPRL_MULTIPART_BOUNDARY HTTP encapsulation boundary string.
HTTPRL_PR_MAX_STRING_LENGTH Max length of a string inside of httprl_pr(). Default is 256KB.
HTTPRL_REQUEST_ABORTED Error code indicating that software caused the connection to be aborted.
HTTPRL_REQUEST_ALLOWED_REDIRECTS_EXHAUSTED Error code indicating that the request made by httprl_request() exceeded the maximum allowed redirects without reaching the final target.
HTTPRL_REQUEST_FWRITE_FAIL Error code indicating that the call to fwrite() failed.
HTTPRL_REQUEST_TIMEOUT Error code indicating that the request exceeded the specified timeout.
HTTPRL_STREAM_SELECT_TIMEOUT Error code indicating that this request made by stream_select() couldn't open a read and/or write to any stream after a minimum of ~10 seconds.
HTTPRL_TIMEOUT Default maximum number of seconds a single request call may take.
HTTPRL_TTFB_TIMEOUT Default maximum number of seconds a connection may take to download the first byte.
HTTPRL_URL_INBOUND_ALTER Run httprl_url_inbound_alter().
HTTPRL_URL_INVALID_SCHEMA Invalid schema. Only http, feed, and https allowed currently.
HTTPRL_URL_MISSING_SCHEMA Given URL is missing a schema (http, https, feed).
HTTPRL_URL_PARSE_ERROR parse_url() was unable to parse the given url.

File

sites/all/modules/ulmus/httprl/httprl.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * HTTP Parallel Request Library module.
  5. */
  6. /**
  7. * Default value
  8. */
  9. define('HTTPRL_BACKGROUND_CALLBACK', TRUE);
  10. /**
  11. * Default maximum number of seconds a single request call may take.
  12. */
  13. define('HTTPRL_TIMEOUT', 30.0);
  14. /**
  15. * Default maximum number of seconds the DNS portion of a request may take.
  16. */
  17. define('HTTPRL_DNS_TIMEOUT', 5.0);
  18. /**
  19. * Default maximum number of seconds establishing the TCP connection of a
  20. * request may take.
  21. */
  22. define('HTTPRL_CONNECT_TIMEOUT', 5.0);
  23. /**
  24. * Default maximum number of seconds a connection may take to download the first
  25. * byte.
  26. */
  27. define('HTTPRL_TTFB_TIMEOUT', 20.0);
  28. /**
  29. * Default maximum number of seconds a function call may take.
  30. */
  31. define('HTTPRL_GLOBAL_TIMEOUT', 120.0);
  32. /**
  33. * Error code indicating that the request made by httprl_request() exceeded
  34. * the maximum allowed redirects without reaching the final target.
  35. */
  36. define('HTTPRL_REQUEST_ALLOWED_REDIRECTS_EXHAUSTED', -2);
  37. /**
  38. * Error code indicating that the call to fwrite() failed.
  39. */
  40. define('HTTPRL_REQUEST_FWRITE_FAIL', -3);
  41. /**
  42. * Error code indicating that all requests made by httprl_send_request
  43. * exceeded the specified timeout.
  44. */
  45. define('HTTPRL_FUNCTION_TIMEOUT', -4);
  46. /**
  47. * Error code indicating that this request made by stream_select() couldn't
  48. * open a read and/or write to any stream after a minimum of ~10 seconds.
  49. */
  50. define('HTTPRL_STREAM_SELECT_TIMEOUT', -5);
  51. /**
  52. * parse_url() was unable to parse the given url.
  53. */
  54. define('HTTPRL_URL_PARSE_ERROR', -1001);
  55. /**
  56. * Given URL is missing a schema (http, https, feed).
  57. */
  58. define('HTTPRL_URL_MISSING_SCHEMA', -1002);
  59. /**
  60. * Invalid schema. Only http, feed, and https allowed currently.
  61. */
  62. define('HTTPRL_URL_INVALID_SCHEMA', -1003);
  63. /**
  64. * An error occurred before the system connect() call. This is most likely due
  65. * to a problem initializing the stream.
  66. */
  67. define('HTTPRL_ERROR_INITIALIZING_STREAM', -1004);
  68. /**
  69. * Error code indicating that software caused the connection to be aborted.
  70. *
  71. * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx
  72. */
  73. define('HTTPRL_REQUEST_ABORTED', -10053);
  74. /**
  75. * Error code indicating that the connection was forcibly closed by the remote
  76. * host.
  77. *
  78. * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx
  79. */
  80. define('HTTPRL_CONNECTION_RESET', -10054);
  81. /**
  82. * Error code indicating that the request exceeded the specified timeout.
  83. *
  84. * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx
  85. */
  86. define('HTTPRL_REQUEST_TIMEOUT', -10060);
  87. /**
  88. * Error code indicating that the endpoint server has refused or dropped the
  89. * connection.
  90. *
  91. * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx
  92. */
  93. define('HTTPRL_CONNECTION_REFUSED', -10061);
  94. /**
  95. * Error code indicating that the host is unknown or can not be found.
  96. *
  97. * @see http://msdn.microsoft.com/en-us/library/aa924071.aspx
  98. */
  99. define('HTTPRL_HOST_NOT_FOUND', -11001);
  100. /**
  101. * HTTP encapsulation boundary string.
  102. */
  103. define('HTTPRL_MULTIPART_BOUNDARY', '---------------------------' . str_replace('.', '', microtime(TRUE)));
  104. /**
  105. * Max length of a string inside of httprl_pr(). Default is 256KB.
  106. */
  107. define('HTTPRL_PR_MAX_STRING_LENGTH', 262144);
  108. /**
  109. * Run httprl_url_inbound_alter().
  110. */
  111. define('HTTPRL_URL_INBOUND_ALTER', TRUE);
  112. /**
  113. * Implements hook_url_inbound_alter().
  114. */
  115. function httprl_url_inbound_alter(&$path, $original_path, $path_language) {
  116. // Do nothing if this has been disabled.
  117. if (!variable_get('httprl_url_inbound_alter', HTTPRL_URL_INBOUND_ALTER)) {
  118. return;
  119. }
  120. // If requested path was for an async callback but now it is something else
  121. // switch is back to the requested path.
  122. $request_path = request_path();
  123. if ($path != $request_path && strpos($request_path, 'httprl_async_function_callback') !== FALSE) {
  124. $path = $request_path;
  125. }
  126. }
  127. /**
  128. * Implements hook_menu().
  129. */
  130. function httprl_menu() {
  131. $items = array();
  132. // Admin page.
  133. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  134. $config_url = 'admin/config/development/httprl';
  135. }
  136. else {
  137. $config_url = 'admin/settings/httprl';
  138. }
  139. $items[$config_url] = array(
  140. 'title' => 'HTTPRL',
  141. 'description' => 'Configure HTTPRL settings.',
  142. 'access arguments' => array('administer site configuration'),
  143. 'page callback' => 'drupal_get_form',
  144. 'page arguments' => array('httprl_admin_settings_form'),
  145. 'type' => MENU_NORMAL_ITEM,
  146. 'file' => 'httprl.admin.inc',
  147. );
  148. // Async Function Callback.
  149. $items['httprl_async_function_callback'] = array(
  150. 'title' => 'HTTPRL',
  151. 'page callback' => 'httprl_async_page',
  152. 'access callback' => TRUE,
  153. 'description' => 'URL for async function workers.',
  154. 'type' => MENU_CALLBACK,
  155. 'file' => 'httprl.async.inc',
  156. );
  157. return $items;
  158. }
  159. /**
  160. * Implements hook_cron().
  161. *
  162. * This hook should be ran about once an hour to once every 5 minutes.
  163. */
  164. function httprl_cron() {
  165. // Let expiration times vary by 5 minutes.
  166. $fuzz_factor = 300;
  167. // Remove expired locks from the semaphore database table.
  168. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  169. db_delete('semaphore')
  170. ->condition('value', 'httprl')
  171. ->condition('expire', REQUEST_TIME - $fuzz_factor, '<')
  172. ->execute();
  173. }
  174. else {
  175. db_query("DELETE FROM {semaphore} WHERE value = 'httprl' AND expire < %f", time() - $fuzz_factor);
  176. }
  177. }
  178. /**
  179. * Queue and send off http request.
  180. *
  181. * @see drupal_http_request()
  182. *
  183. * This is a flexible and powerful HTTP client implementation. Correctly
  184. * handles GET, POST, PUT or any other HTTP requests.
  185. *
  186. * @param string $url
  187. * A string containing a fully qualified URI.
  188. * @param array $options
  189. * (optional) An array of options.
  190. *
  191. * @return object
  192. * The request object.
  193. */
  194. function httprl_override_core($url, $options = array()) {
  195. // Queue up the request.
  196. httprl_request($url, $options);
  197. // Execute request.
  198. $request = httprl_send_request();
  199. // Send back results.
  200. return is_array($request) && is_string($url) && array_key_exists($url, $request) ? $request[$url] : array_pop($request);
  201. }
  202. /**
  203. * Helper function to build an URL for asynchronous requests to self.
  204. *
  205. * @param string $level
  206. * How deep to go when setting the base path.
  207. */
  208. function _httprl_build_drupal_root($level = 0) {
  209. static $webroot;
  210. $root_path = '/';
  211. if ($level > 0) {
  212. // Work backwards from this file till we find drupal's index.php.
  213. if (!isset($webroot)) {
  214. $webroot = str_replace('\\', '/', dirname(__FILE__));
  215. while (!empty($webroot)) {
  216. if (file_exists($webroot . '/index.php') && strpos(file_get_contents($webroot . '/index.php'), 'menu_execute_active_handler();') !== FALSE) {
  217. break;
  218. }
  219. $new_webroot = str_replace('\\', '/', dirname($webroot));
  220. if ($new_webroot == $webroot) {
  221. $webroot = str_replace('\\', '/', getcwd());
  222. break;
  223. }
  224. $webroot = $new_webroot;
  225. }
  226. }
  227. $root_path = '';
  228. $webroot_array = explode('/', $webroot);
  229. while ($level > 0 && count($webroot_array) != 0) {
  230. $level--;
  231. $root_path = array_pop($webroot_array) . '/' . $root_path;
  232. }
  233. $root_path = '/' . $root_path;
  234. }
  235. else {
  236. if (!empty($GLOBALS['base_path'])) {
  237. $root_path = $GLOBALS['base_path'];
  238. }
  239. }
  240. // Server auth.
  241. $auth = '';
  242. if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic') {
  243. $auth = $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'] . '@';
  244. }
  245. // Get Host.
  246. $ip = httprl_variable_get('httprl_server_addr', FALSE);
  247. if ($ip == -1) {
  248. $ip = $_SERVER['HTTP_HOST'];
  249. // If the host is bad don't use it.
  250. if (isset($_SERVER['argc']) || isset($_SERVER['argv'])) {
  251. if (gethostbyname($_SERVER['HTTP_HOST']) == $_SERVER['HTTP_HOST']) {
  252. $ip = '';
  253. }
  254. }
  255. }
  256. if (empty($ip)) {
  257. $ip = empty($_SERVER['SERVER_ADDR']) ? '127.0.0.1' : $_SERVER['SERVER_ADDR'];
  258. // Check for IPv6. If IPv6 convert to IPv4 if possible.
  259. if (strpos($ip, ':') !== FALSE) {
  260. if ($_SERVER['SERVER_ADDR'] == '::1') {
  261. $ip = "127.0.0.1";
  262. }
  263. elseif (preg_match('/^::\d+.\d+.\d+.\d+$/', $ip)) {
  264. $ip = substr($ip, 2);
  265. }
  266. elseif (!empty($_SERVER['HTTP_HOST'])) {
  267. // Last option is to use the IP from the host name.
  268. $ip = gethostbyname($_SERVER['HTTP_HOST']);
  269. if (gethostbyname($_SERVER['HTTP_HOST']) == $_SERVER['HTTP_HOST']) {
  270. $ip = '';
  271. }
  272. }
  273. }
  274. }
  275. if (empty($ip)) {
  276. $ip = '127.0.0.1';
  277. }
  278. // Port.
  279. $port = '';
  280. // if ( isset($_SERVER['SERVER_PORT'])
  281. // && is_numeric($_SERVER['SERVER_PORT'])
  282. // && ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443)
  283. // ) {
  284. // $port = ':' . $_SERVER['SERVER_PORT'];
  285. // }
  286. // URL schema http or https.
  287. $schema = httprl_get_server_schema() . '://';
  288. // Special handling if clean urls are disabled.
  289. if (!variable_get('clean_url', 0)) {
  290. $path_parts = @parse_url('http://example.com/' . $path);
  291. if (!empty($path_parts)) {
  292. $path_parts_query = array();
  293. if (isset($path_parts['query'])) {
  294. parse_str($path_parts['query'], $path_parts_query);
  295. }
  296. $path_parts_query['q'] = ltrim($path_parts['path'], '/');
  297. $path = '?' . http_build_query($path_parts_query, '', '&');
  298. }
  299. }
  300. return $schema . $auth . $ip . $port . $root_path;
  301. }
  302. /**
  303. * Helper function to build an URL for asynchronous requests to self.
  304. *
  305. * @param string $path
  306. * Path to a URI excluding everything to the left and including the base path.
  307. * @param bool $detect_schema
  308. * If TRUE it will see if this request is https; if so, it will set the full
  309. * url to be https as well.
  310. */
  311. function httprl_build_url_self($path = '', $detect_schema = FALSE) {
  312. static $drupal_root;
  313. if (!isset($drupal_root)) {
  314. $drupal_root = _httprl_build_drupal_root();
  315. // If ran from the command line, the drupal root might be in a subdir. Test to
  316. // make sure we have the right directory.
  317. if (isset($_SERVER['argc']) || isset($_SERVER['argv'])) {
  318. $level = 0;
  319. $found = FALSE;
  320. while (!$found) {
  321. // Trick due to not knowing the subdir.
  322. // http://stackoverflow.com/questions/8361355/get-apache-document-root-from-command-line-execution-no-browser/8415235#8415235
  323. $headers = get_headers($drupal_root . 'httprl_async_function_callback');
  324. if (!empty($headers)) {
  325. foreach ($headers as $header) {
  326. if (stripos($header, 'X-HTTPRL') !== FALSE) {
  327. $found = TRUE;
  328. }
  329. }
  330. }
  331. if (!$found) {
  332. $level++;
  333. $new_drupal_root = _httprl_build_drupal_root($level);
  334. if ($new_drupal_root == $drupal_root) {
  335. // Use no subdirectories if nothing worked.
  336. $drupal_root = _httprl_build_drupal_root();
  337. break;
  338. }
  339. $drupal_root = $new_drupal_root;
  340. }
  341. }
  342. }
  343. }
  344. return $drupal_root . $path;
  345. }
  346. /**
  347. * Run parse_url and handle any errors.
  348. *
  349. * @param string $url
  350. * String containing the URL to be parsed by parse_url().
  351. * @param object &$result
  352. * Result object; used only for error handling in this function.
  353. *
  354. * @return array
  355. * Array from parse_url().
  356. */
  357. function httprl_parse_url($url, &$result) {
  358. // Parse the URL and make sure we can handle the schema.
  359. $uri = @parse_url($url);
  360. // If the t function is not available use httprl_pr.
  361. $t = function_exists('t') ? 't' : 'httprl_pr';
  362. if (empty($uri)) {
  363. // Set error code for failed request.
  364. $result->error = $t('Unable to parse URL.');
  365. $result->code = HTTPRL_URL_PARSE_ERROR;
  366. }
  367. elseif (!isset($uri['scheme'])) {
  368. // Set error code for failed request.
  369. $result->error = $t('Missing schema.');
  370. $result->code = HTTPRL_URL_MISSING_SCHEMA;
  371. }
  372. return $uri;
  373. }
  374. /**
  375. * Set the default options in the $options array.
  376. *
  377. * @param array &$options
  378. * Array containing options.
  379. */
  380. function httprl_set_default_options(&$options) {
  381. global $base_root;
  382. // Merge the default options.
  383. $options += array(
  384. 'headers' => array(),
  385. 'method' => 'GET',
  386. 'data' => NULL,
  387. 'max_redirects' => 3,
  388. 'timeout' => httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT),
  389. 'dns_timeout' => httprl_variable_get('httprl_dns_timeout', HTTPRL_DNS_TIMEOUT),
  390. 'connect_timeout' => httprl_variable_get('httprl_connect_timeout', HTTPRL_CONNECT_TIMEOUT),
  391. 'ttfb_timeout' => httprl_variable_get('httprl_ttfb_timeout', HTTPRL_TTFB_TIMEOUT),
  392. 'context' => NULL,
  393. 'secure_socket_transport' => 'ssl',
  394. 'blocking' => TRUE,
  395. 'version' => '1.0',
  396. 'referrer' => FALSE,
  397. 'domain_connections' => 2,
  398. 'global_connections' => 128,
  399. 'global_timeout' => httprl_variable_get('httprl_global_timeout', HTTPRL_GLOBAL_TIMEOUT),
  400. 'chunk_size_read' => 32768,
  401. 'chunk_size_write' => 1024,
  402. 'async_connect' => TRUE,
  403. 'ping_db' => 20,
  404. );
  405. // Adjust Time To First Byte Timeout if timeout is large and ttfb is default.
  406. if ($options['timeout'] > httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT) && $options['ttfb_timeout'] == httprl_variable_get('httprl_ttfb_timeout', HTTPRL_TTFB_TIMEOUT)) {
  407. $options['ttfb_timeout'] = $options['timeout'] - max(1, httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT) - httprl_variable_get('httprl_ttfb_timeout', HTTPRL_TTFB_TIMEOUT));
  408. }
  409. // Adjust Global Timeout if timeout is large and global_timeout is default.
  410. if ($options['timeout'] > httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT) && $options['global_timeout'] == httprl_variable_get('httprl_global_timeout', HTTPRL_GLOBAL_TIMEOUT)) {
  411. $options['global_timeout'] = $options['timeout'] + max(1, httprl_variable_get('httprl_global_timeout', HTTPRL_GLOBAL_TIMEOUT) - httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT));
  412. }
  413. // Merge the default headers.
  414. // Set user agent to drupal.
  415. // Set connection to closed to prevent keep-alive from causing a timeout.
  416. $options['headers'] += array(
  417. 'User-Agent' => 'Drupal (+http://drupal.org/)',
  418. 'Connection' => 'close',
  419. );
  420. // Set referrer to current page.
  421. if (!isset($options['headers']['Referer']) && !empty($options['referrer'])) {
  422. if (function_exists('request_uri')) {
  423. $options['headers']['Referer'] = $base_root . request_uri();
  424. }
  425. }
  426. // stream_socket_client() requires timeout to be a float.
  427. $options['timeout'] = (float) $options['timeout'];
  428. }
  429. /**
  430. * If server uses a proxy, change the request to utilize said proxy.
  431. *
  432. * @param array &$uri
  433. * Array from parse_url().
  434. * @param array &$options
  435. * Array containing options.
  436. * @param string $url
  437. * String containing the URL.
  438. *
  439. * @return string
  440. * String containing the proxy servers host name if one is to be used.
  441. */
  442. function httprl_setup_proxy(&$uri, &$options, $url) {
  443. // Proxy setup.
  444. $proxy_server = httprl_variable_get('proxy_server', '');
  445. // Use a proxy if one is defined and the host is not on the excluded list.
  446. if ($proxy_server && _httprl_use_proxy($uri['host'])) {
  447. // Set the scheme so we open a socket to the proxy server.
  448. $uri['scheme'] = 'proxy';
  449. // Set the path to be the full URL.
  450. $uri['path'] = $url;
  451. // Since the full URL is passed as the path, we won't use the parsed query.
  452. unset($uri['query']);
  453. // Add in username and password to Proxy-Authorization header if needed.
  454. if ($proxy_username = httprl_variable_get('proxy_username', '')) {
  455. $proxy_password = httprl_variable_get('proxy_password', '');
  456. $options['headers']['Proxy-Authorization'] = 'Basic ' . base64_encode($proxy_username . ':' . $proxy_password);
  457. }
  458. // Some proxies reject requests with any User-Agent headers, while others
  459. // require a specific one.
  460. $proxy_user_agent = httprl_variable_get('proxy_user_agent', '');
  461. // The default value matches neither condition.
  462. if (is_null($proxy_user_agent)) {
  463. unset($options['headers']['User-Agent']);
  464. }
  465. elseif ($proxy_user_agent) {
  466. $options['headers']['User-Agent'] = $proxy_user_agent;
  467. }
  468. }
  469. return $proxy_server;
  470. }
  471. /**
  472. * Create the TCP/SSL socket connection string.
  473. *
  474. * @param array $uri
  475. * Array from parse_url().
  476. * @param array &$options
  477. * Array containing options.
  478. * @param string $proxy_server
  479. * String containing the proxy servers host name if one is to be used.
  480. * @param object &$result
  481. * Result object; used only for error handling in this function.
  482. *
  483. * @return string
  484. * String containing the TCP/SSL socket connection URI.
  485. */
  486. function httprl_set_socket($uri, &$options, $proxy_server, &$result) {
  487. $socket = '';
  488. switch ($uri['scheme']) {
  489. case 'proxy':
  490. // Make the socket connection to a proxy server.
  491. $socket = 'tcp://' . $proxy_server . ':' . httprl_variable_get('proxy_port', 8080);
  492. // The Host header still needs to match the real request.
  493. $options['headers']['Host'] = $uri['host'];
  494. $options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
  495. break;
  496. case 'http':
  497. case 'feed':
  498. $port = isset($uri['port']) ? $uri['port'] : 80;
  499. $socket = 'tcp://' . $uri['host'] . ':' . $port;
  500. // RFC 2616: "non-standard ports MUST, default ports MAY be included".
  501. // We don't add the standard port to prevent from breaking rewrite rules
  502. // checking the host that do not take into account the port number.
  503. if (empty($options['headers']['Host'])) {
  504. $options['headers']['Host'] = $uri['host'];
  505. }
  506. if ($port != 80) {
  507. $options['headers']['Host'] .= ':' . $port;
  508. }
  509. break;
  510. case 'https':
  511. // Note: Only works when PHP is compiled with OpenSSL support.
  512. $port = isset($uri['port']) ? $uri['port'] : 443;
  513. $socket = $options['secure_socket_transport'] . '://' . $uri['host'] . ':' . $port;
  514. if (empty($options['headers']['Host'])) {
  515. $options['headers']['Host'] = $uri['host'];
  516. }
  517. if ($port != 443) {
  518. $options['headers']['Host'] .= ':' . $port;
  519. }
  520. break;
  521. default:
  522. // If the t function is not available use httprl_pr.
  523. $t = function_exists('t') ? 't' : 'httprl_pr';
  524. $result->error = $t('Invalid schema @scheme.', array('@scheme' => $uri['scheme']));
  525. $result->code = HTTPRL_URL_INVALID_SCHEMA;
  526. }
  527. return $socket;
  528. }
  529. /**
  530. * Select which connect flags to use in stream_socket_client().
  531. *
  532. * @param array &$options
  533. * Array containing options.
  534. * @param array $uri
  535. * Array from parse_url().
  536. *
  537. * @return int
  538. * STREAM_CLIENT_CONNECT or STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT.
  539. */
  540. function httprl_set_connection_flag(&$options, $uri) {
  541. $flags = STREAM_CLIENT_CONNECT;
  542. // Set connection flag.
  543. if ($options['async_connect']) {
  544. // Workaround for PHP bug with STREAM_CLIENT_ASYNC_CONNECT and SSL
  545. // https://bugs.php.net/bug.php?id=48182 - Fixed in PHP 5.2.11 and 5.3.1
  546. if ($uri['scheme'] == 'https' && (version_compare(PHP_VERSION, '5.2.11', '<') || version_compare(PHP_VERSION, '5.3.0', '='))) {
  547. $options['async_connect'] = FALSE;
  548. }
  549. else {
  550. $flags = STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT;
  551. }
  552. }
  553. return $flags;
  554. }
  555. /**
  556. * If data is being sent out in this request, handle it correctly.
  557. *
  558. * If $options['data'] is not a string, convert it to a string using
  559. * http_build_query(). Set the Content-Length header correctly. Set the
  560. * Content-Type to application/x-www-form-urlencoded if not already set and
  561. * using method is POST.
  562. *
  563. * @todo
  564. * Proper mime support.
  565. *
  566. * @param array &$options
  567. * Array containing options.
  568. */
  569. function httprl_handle_data(&$options) {
  570. // Encode data if not already done.
  571. if (isset($options['data']) && !is_string($options['data'])) {
  572. // Record raw data before it gets processed.
  573. $options['data-input'] = $options['data'];
  574. if (!empty($options['headers']['Content-Type']) && strpos($options['headers']['Content-Type'], 'multipart/related') === 0 && !empty($options['data'])) {
  575. // Trim semicolon from Content-Type header if needed.
  576. $options['headers']['Content-Type'] = trim($options['headers']['Content-Type']);
  577. if (substr_compare($options['headers']['Content-Type'], ';', -1, 1) === 0) {
  578. $options['headers']['Content-Type'] = substr($options['headers']['Content-Type'], -1);
  579. }
  580. // Add in boundary.
  581. $options['headers']['Content-Type'] .= '; boundary=' . HTTPRL_MULTIPART_BOUNDARY;
  582. $data_stream = '';
  583. foreach ($options['data'] as $part) {
  584. $data_stream .= '--' . HTTPRL_MULTIPART_BOUNDARY . "\r\n";
  585. foreach ($part['headers'] as $key => $value) {
  586. $data_stream .= $key . ': ' . $value . "\r\n";
  587. }
  588. $data_stream .= "\r\n";
  589. if (isset($part['file'])) {
  590. $data_stream .= file_get_contents($part['file']) . "\r\n";
  591. }
  592. elseif (isset($part['string'])) {
  593. $data_stream .= $part['string'] . "\r\n";
  594. }
  595. }
  596. // Signal end of request (note the trailing "--").
  597. $data_stream .= '--' . HTTPRL_MULTIPART_BOUNDARY . "--\r\n";
  598. $options['data'] = $data_stream;
  599. }
  600. // No files passed in, url-encode the data.
  601. elseif (empty($options['data']['files']) || !is_array($options['data']['files'])) {
  602. $options['data'] = http_build_query($options['data'], '', '&');
  603. // Set the Content-Type to application/x-www-form-urlencoded if the data
  604. // is not empty, the Content-Type is not set, and the method is POST or
  605. // PUT.
  606. if (!empty($options['data']) && !isset($options['headers']['Content-Type']) && ($options['method'] == 'POST' || $options['method'] == 'PUT')) {
  607. $options['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
  608. }
  609. }
  610. else {
  611. $data_stream = '';
  612. // Add files to the request.
  613. foreach ($options['data']['files'] as $field_name => $info) {
  614. $multi_field = '[]';
  615. // Convert $info into an array if it's a string.
  616. // This makes for one code path (the foreach loop).
  617. if (is_string($info)) {
  618. $multi_field = '';
  619. $temp = $info;
  620. unset($info);
  621. $info[] = $temp;
  622. }
  623. foreach ($info as $fullpath) {
  624. // Strip '@' from the start of the path (cURL requirement).
  625. if (substr($fullpath, 0, 1) == "@") {
  626. $fullpath = substr($fullpath, 1);
  627. }
  628. $filename = basename($fullpath);
  629. // TODO: mime detection.
  630. $mimetype = 'application/octet-stream';
  631. // Build the data-stream for this file.
  632. $data_stream .= '--' . HTTPRL_MULTIPART_BOUNDARY . "\r\n";
  633. $data_stream .= 'Content-Disposition: form-data; name="files[' . $field_name . ']' . $multi_field . '"; filename="' . $filename . "\"\r\n";
  634. $data_stream .= 'Content-Transfer-Encoding: binary' . "\r\n";
  635. $data_stream .= 'Content-Type: ' . $mimetype . "\r\n\r\n";
  636. $data_stream .= file_get_contents($fullpath) . "\r\n";
  637. }
  638. }
  639. // Remove files from the data array as they have already been added.
  640. $data_array = $options['data'];
  641. unset($data_array['files']);
  642. // Add fields to the request too: $_POST['foo'] = 'bar'.
  643. httprl_multipart_encoder($data_stream, $data_array);
  644. // Signal end of request (note the trailing "--").
  645. $data_stream .= '--' . HTTPRL_MULTIPART_BOUNDARY . "--\r\n";
  646. $options['data'] = $data_stream;
  647. // Set the Content-Type to multipart/form-data if the data is not empty,
  648. // the Content-Type is not set, and the method is POST or PUT.
  649. if (!empty($options['data']) && !isset($options['headers']['Content-Type']) && ($options['method'] == 'POST' || $options['method'] == 'PUT')) {
  650. $options['headers']['Content-Type'] = 'multipart/form-data; boundary=' . HTTPRL_MULTIPART_BOUNDARY;
  651. }
  652. }
  653. }
  654. // Only add Content-Length if we actually have any content or if it is a POST
  655. // or PUT request. Some non-standard servers get confused by Content-Length in
  656. // at least HEAD/GET requests, and Squid always requires Content-Length in
  657. // POST/PUT requests.
  658. if (strlen($options['data']) > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
  659. $options['headers']['Content-Length'] = httprl_strlen($options['data']);
  660. }
  661. }
  662. /**
  663. * Multipart encode a data array.
  664. *
  665. * PHP has http_build_query() which will url-encode data. There is no built in
  666. * function to multipart encode data thus we have this function below.
  667. *
  668. * @param string &$data_stream
  669. * Appended with all multi-part headers.
  670. * @param array $data_array
  671. * Array of data in key => value pairs.
  672. * @param array $prepend
  673. * (optional) key => values pairs to prepend to $data_array.
  674. */
  675. function httprl_multipart_encoder(&$data_stream, $data_array, $prepend = array()) {
  676. foreach ($data_array as $key => $value) {
  677. $key_array = $prepend;
  678. $key_array[] = $key;
  679. if (is_array($value)) {
  680. httprl_multipart_encoder($data_stream, $value, $key_array);
  681. }
  682. elseif (is_scalar($value)) {
  683. $key_string = array_shift($key_array);
  684. if (!empty($key_array)) {
  685. $key_string .= '[' . implode('][', $key_array) . ']';
  686. }
  687. $data_stream .= '--' . HTTPRL_MULTIPART_BOUNDARY . "\r\n";
  688. $data_stream .= 'Content-Disposition: form-data; name="' . $key_string . "\"\r\n\r\n";
  689. $data_stream .= $value . "\r\n";
  690. }
  691. }
  692. }
  693. /**
  694. * Set the Authorization header if a user is set in the URI.
  695. *
  696. * @param array $uri
  697. * Array from parse_url().
  698. * @param array &$options
  699. * Array containing options.
  700. */
  701. function httprl_basic_auth($uri, &$options) {
  702. // If the server URL has a user then attempt to use basic authentication.
  703. if (isset($uri['user'])) {
  704. $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . ':' . (isset($uri['pass']) ? $uri['pass'] : ''));
  705. }
  706. }
  707. /**
  708. * Build the request string.
  709. *
  710. * This string is what gets sent to the server once a connection has been made.
  711. *
  712. * @param array $uri
  713. * Array from parse_url().
  714. * @param array $options
  715. * Array containing options.
  716. *
  717. * @return string
  718. * String containing the data that will be written to the server.
  719. */
  720. function httprl_build_request_string($uri, $options) {
  721. // Construct the path to act on.
  722. $path = isset($uri['path']) ? $uri['path'] : '/';
  723. if (isset($uri['query'])) {
  724. $path .= '?' . $uri['query'];
  725. }
  726. // Assemble the request together. HTTP version requires to be a float.
  727. $request = $options['method'] . ' ' . $path . ' HTTP/' . sprintf("%.1F", $options['version']) . "\r\n";
  728. foreach ($options['headers'] as $name => $value) {
  729. $request .= $name . ': ' . trim($value) . "\r\n";
  730. }
  731. return $request . "\r\n" . $options['data'];
  732. }
  733. /**
  734. * Read the error number & string and give a nice looking error in the output.
  735. *
  736. * This is a flexible and powerful HTTP client implementation. Correctly
  737. * handles GET, POST, PUT or any other HTTP requests.
  738. *
  739. * @param int $errno
  740. * Error number from stream_socket_client().
  741. * @param string $errstr
  742. * Error string from stream_socket_client().
  743. * @param object $result
  744. * An object for httprl_send_request.
  745. */
  746. function httprl_stream_connection_error_formatter($errno, $errstr, &$result) {
  747. // If the t function is not available use httprl_pr.
  748. $t = function_exists('t') ? 't' : 'httprl_pr';
  749. if (function_exists('t')) {
  750. // Make sure drupal_convert_to_utf8() is available.
  751. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  752. require_once DRUPAL_ROOT . '/includes/unicode.inc';
  753. }
  754. else {
  755. require_once './includes/unicode.inc';
  756. }
  757. // Convert error message to utf-8. Using ISO-8859-1 (Latin-1) as source
  758. // encoding could be wrong; it is a simple workaround :)
  759. $errstr = trim(drupal_convert_to_utf8($errstr, 'ISO-8859-1'));
  760. }
  761. if (!$errno) {
  762. // If $errno is 0, it is an indication that the error occurred
  763. // before the connect() call.
  764. if (empty($errstr)) {
  765. // If the error string is empty as well, this is most likely due to a
  766. // problem initializing the stream.
  767. $result->code = HTTPRL_ERROR_INITIALIZING_STREAM;
  768. $result->error = $t('Error initializing socket @socket.', array('@socket' => $result->socket));
  769. }
  770. elseif (stripos($errstr, 'network_getaddresses: getaddrinfo failed:') !== FALSE) {
  771. // Host not found. No such host is known. The name is not an official host
  772. // name or alias.
  773. $result->code = HTTPRL_HOST_NOT_FOUND;
  774. $result->error = $errstr;
  775. }
  776. }
  777. elseif ($errno == 110) {
  778. // 110 means Connection timed out. This should be HTTPRL_REQUEST_TIMEOUT.
  779. $result->code = HTTPRL_REQUEST_TIMEOUT;
  780. $result->error = !empty($errstr) ? $errstr : $t('Connection timed out. TCP.');
  781. }
  782. else {
  783. // When a network error occurs, we use a negative number so it does not
  784. // clash with the HTTP status codes.
  785. $result->code = (int) - $errno;
  786. $result->error = !empty($errstr) ? $errstr : $t('Error opening socket @socket.', array('@socket' => $result->socket));
  787. }
  788. }
  789. /**
  790. * Use stream_socket_client() to create a connection to the server.
  791. *
  792. * @param object $result
  793. * A object to hold the result values.
  794. */
  795. function httprl_establish_stream_connection(&$result) {
  796. // Record start time.
  797. $start_time = microtime(TRUE);
  798. $result->fp = FALSE;
  799. // Try to make a connection, 3 max tries in loop.
  800. $count = 0;
  801. while (!$result->fp && $count < 3) {
  802. // Try the connection again not using async if in https mode.
  803. if ($count > 0) {
  804. if ($result->flags === STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT && $result->uri['scheme'] == 'https') {
  805. $result->flags = STREAM_CLIENT_CONNECT;
  806. $result->options['async_connect'] = FALSE;
  807. }
  808. else {
  809. // Break out of while loop if we can't connect.
  810. break;
  811. }
  812. }
  813. // Set the DNS timeout.
  814. $timeout = $result->options['dns_timeout'];
  815. // If not using async_connect then add connect_timeout to timeout.
  816. if (!$result->options['async_connect']) {
  817. $timeout += $result->options['connect_timeout'];
  818. }
  819. // Open the connection.
  820. if (empty($result->options['context'])) {
  821. $result->fp = @stream_socket_client($result->socket, $errno, $errstr, $timeout, $result->flags);
  822. }
  823. else {
  824. // Create a stream with context. Context allows for the verification of
  825. // a SSL certificate.
  826. $result->fp = @stream_socket_client($result->socket, $errno, $errstr, $timeout, $result->flags, $result->options['context']);
  827. }
  828. $count++;
  829. }
  830. // Make sure the stream opened properly. This check doesn't work if
  831. // async_connect is used, so only check it if async_connect is FALSE. Making
  832. // sure that stream_socket_get_name returns a "TRUE" value.
  833. if ( $result->fp
  834. && !$result->options['async_connect']
  835. && !stream_socket_get_name($result->fp, TRUE)
  836. ) {
  837. $errno = HTTPRL_CONNECTION_REFUSED;
  838. $errstr = 'Connection refused. No connection could be made because the target machine actively refused it.';
  839. $result->fp = FALSE;
  840. }
  841. // Report any errors or set the stream to non blocking mode.
  842. if (!$result->fp) {
  843. httprl_stream_connection_error_formatter($errno, $errstr, $result);
  844. }
  845. else {
  846. stream_set_blocking($result->fp, 0);
  847. }
  848. // Record end time.
  849. $end_time = microtime(TRUE);
  850. $extra = 0;
  851. if (isset($result->options['internal_states']['running_time'])) {
  852. $extra = $result->options['internal_states']['running_time'];
  853. unset($result->options['internal_states']['running_time']);
  854. }
  855. $result->running_time = $end_time - $start_time + $extra;
  856. }
  857. /**
  858. * Queue up a HTTP request in httprl_send_request.
  859. *
  860. * @see drupal_http_request()
  861. *
  862. * This is a flexible and powerful HTTP client implementation. Correctly
  863. * handles GET, POST, PUT or any other HTTP requests.
  864. *
  865. * @param string|array $urls
  866. * A string or an array containing a fully qualified URI(s).
  867. * @param array $options
  868. * (optional) An array that can have one or more of the following elements:
  869. * - headers: An array containing request headers to send as name/value pairs.
  870. * Some of the more useful headers:
  871. * - For POST: 'Content-Type' => 'application/x-www-form-urlencoded',
  872. * - Limit number of bytes server sends back: 'Range' => 'bytes=0-1024',
  873. * - Compression: 'Accept-Encoding' => 'gzip, deflate',
  874. * - Let server know where request came from: 'Referer' => 'example.com',
  875. * - Content-Types that are acceptable: 'Accept' => 'text/plain',
  876. * - Send Cookies: 'Cookie' => 'key1=value1; key2=value2;',
  877. * - Skip the cache: 'Cache-Control' => 'no-cache',
  878. * - Skip the cache: 'Pragma' => 'no-cache',
  879. * List of headers: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
  880. * - method: A string containing the request method. Defaults to 'GET'.
  881. * - data: A string containing the request body, formatted as
  882. * 'param=value&param=value&...'. Defaults to NULL.
  883. * - max_redirects: An integer representing how many times a redirect
  884. * may be followed. Defaults to 3.
  885. * - timeout: A float representing the maximum number of seconds a connection
  886. * may take. The default is 30 seconds. If a timeout occurs, the error code
  887. * is set to the HTTPRL_REQUEST_ABORTED constant.
  888. * - dns_timeout: A float representing the maximum number of seconds a DNS
  889. * lookup request may take. The default is 5 seconds. If a timeout occurs,
  890. * the error code is set to the HTTPRL_HOST_NOT_FOUND constant.
  891. * - connect_timeout: A float representing the maximum number of seconds
  892. * establishing the TCP connection may take. The default is 5 seconds. If a
  893. * timeout occurs, the error code is set to the HTTPRL_REQUEST_TIMEOUT
  894. * constant.
  895. * - ttfb_timeout: A float representing the maximum number of seconds a
  896. * connection may take to download the first byte. The default is 20
  897. * seconds. If a timeout occurs, the error code is set to the
  898. * HTTPRL_REQUEST_ABORTED constant.
  899. * - context: A context resource created with stream_context_create().
  900. * - secure_socket_transport: The transport to use when making secure
  901. * requests over HTTPS; see http://php.net/manual/en/transports.inet.php
  902. * for more information. The value should be 'ssl', 'sslv2', 'sslv3' or
  903. * 'tls'. Defaults to 'ssl', which will work for HTTPS requests to most
  904. * remote servers.
  905. * - blocking: set to FALSE to make this not care about the returned data.
  906. * - version: HTTP Version 1.0 or 1.1. Default is 1.0 for a good reason.
  907. * - referrer: TRUE - send current page; FALSE - do not send current
  908. * page. Default is FALSE.
  909. * - domain_connections: Maximum number of simultaneous connections to a given
  910. * domain name. Default is 8.
  911. * - global_connections: Maximum number of simultaneous connections that can
  912. * be open on the server. Default is 128.
  913. * - global_timeout: A float representing the maximum number of seconds the
  914. * function call may take. If a timeout occurs,the error code is set to the
  915. * HTTPRL_FUNCTION_TIMEOUT constant. Default is 120 seconds.
  916. * - chunk_size_write: max size of what will be written in fwrite().
  917. * - chunk_size_read: max size of what will be read from fread().
  918. * - async_connect: default is TRUE. FALSE may give more info on errors but is
  919. * generally slower.
  920. * - callback: Array where the first value is an array of options; the result
  921. * is passed to the callback function as the first argument, the other
  922. * options passed in this array are passed in after the result. The options
  923. * array needs to contain the function name and the target variable for the
  924. * result of the function.
  925. * - background_callback: Array where the first value is an array of options;
  926. * the result is passed to the callback function as the first argument, the
  927. * other options passed in this array are passed in after the result. The
  928. * options array needs to contain the function name. If the return or
  929. * printed keys are not defined this function will run in non blocking mode
  930. * and the parent will not be able to get the result; if the return or
  931. * printed keys defined then this function will run in blocking mode and the
  932. * returned and printed data as well as any variables passed by reference
  933. * will be available to the parent.
  934. * - alter_all_streams_function: Function name. This function runs at the end
  935. * of httprl_post_processing() so that one can alter the $responses and
  936. * $output variables inside of httprl_send_request. Defined function
  937. * should have the following parameters:
  938. * ($id, &$responses).
  939. * - stall_fread: TRUE or FALSE. If true once all fwrites have been done
  940. * httprl_send_request() will return. You will need to call
  941. * httprl_send_request() a second time to read the responses back.
  942. * - ping_db: After X amount of time, ping the DB with a simple query in order
  943. * to keep the connection alive. Default is every 20 seconds. Set to 0 to
  944. * disable.
  945. *
  946. * @return array
  947. * Array where key is the URL and the value is the return value from
  948. * httprl_send_request.
  949. */
  950. function httprl_request($urls, $options = array()) {
  951. // See if a full bootstrap has been done.
  952. $full_bootstrap = httprl_drupal_full_bootstrap();
  953. // Transform string to an array.
  954. if (!is_array($urls)) {
  955. $temp = &$urls;
  956. unset($urls);
  957. $urls = array(&$temp);
  958. unset($temp);
  959. }
  960. if ($full_bootstrap) {
  961. // Allow other modules to alter things before we get started.
  962. // Run hook_pre_httprl_request_alter().
  963. $data = array($urls, $options);
  964. drupal_alter('pre_httprl_request', $data);
  965. list($urls, $options) = $data;
  966. }
  967. $connections = array();
  968. $return = array();
  969. // Set things up; but do not perform any IO.
  970. foreach ($urls as &$url) {
  971. $result = new stdClass();
  972. $result->url = &$url;
  973. $result->status = 'Connecting.';
  974. $result->code = 0;
  975. $result->chunk_size = 1024;
  976. $result->data = '';
  977. // Copy Options.
  978. $these_options = $options;
  979. // Setup the default options.
  980. httprl_set_default_options($these_options);
  981. // Parse the given URL and skip if an error occurred.
  982. $uri = httprl_parse_url($url, $result);
  983. if (isset($result->error)) {
  984. // Put all variables into an array for easy alterations.
  985. $connections[] = array(NULL, NULL, $uri, $url, $these_options, $result, NULL);
  986. $return[$url] = FALSE;
  987. // Stop processing this request as we have encountered an error.
  988. continue;
  989. }
  990. // Set the proxy server if one is required.
  991. $proxy_server = httprl_setup_proxy($uri, $these_options, $url);
  992. // Create the socket string and skip if an error occurred.
  993. $socket = httprl_set_socket($uri, $these_options, $proxy_server, $result, $return, $url);
  994. if (isset($result->error)) {
  995. // Put all variables into an array for easy alterations.
  996. $connections[] = array($socket, NULL, $uri, $url, $these_options, $result, NULL);
  997. $return[$url] = FALSE;
  998. // Stop processing this request as we have encountered an error.
  999. continue;
  1000. }
  1001. // Use a sync or async connection.
  1002. $flags = httprl_set_connection_flag($these_options, $uri);
  1003. // Set basic authorization header if needed.
  1004. httprl_basic_auth($uri, $these_options);
  1005. // If any data is given, do the right things to this request so it works.
  1006. httprl_handle_data($these_options);
  1007. // Build the request string.
  1008. $request = httprl_build_request_string($uri, $these_options);
  1009. // Put all variables into an array for easy alterations.
  1010. $connections[] = array($socket, $flags, $uri, $url, $these_options, $result, $request);
  1011. $return[$url] = TRUE;
  1012. }
  1013. if ($full_bootstrap) {
  1014. // Allow other programs to alter the connections before they are made.
  1015. // run hook_httprl_request_alter().
  1016. drupal_alter('httprl_request', $connections);
  1017. }
  1018. $results = array();
  1019. foreach ($connections as $connection) {
  1020. list($socket, $flags, $uri, $url, $options, $result, $request) = $connection;
  1021. $result->request = $request;
  1022. $result->options = $options;
  1023. $result->socket = $socket;
  1024. $result->flags = $flags;
  1025. $result->uri = $uri;
  1026. $result->running_time = 0;
  1027. $results[] = $result;
  1028. }
  1029. httprl_send_request($results);
  1030. return $return;
  1031. }
  1032. /**
  1033. * Perform many HTTP requests.
  1034. *
  1035. * @see drupal_http_request()
  1036. *
  1037. * This is a flexible and powerful HTTP client implementation. Correctly
  1038. * handles GET, POST, PUT or any other HTTP requests.
  1039. *
  1040. * @param $results
  1041. * (optional) Array of results.
  1042. *
  1043. * @return bool
  1044. * TRUE if function worked as planed.
  1045. */
  1046. function httprl_send_request($results = NULL) {
  1047. static $responses = array();
  1048. static $counter = 0;
  1049. static $output = array();
  1050. static $static_stall_freads = FALSE;
  1051. if (!is_null($results)) {
  1052. // Put the connection information into the responses array.
  1053. foreach ($results as $result) {
  1054. $responses[$counter] = $result;
  1055. $counter++;
  1056. }
  1057. return TRUE;
  1058. }
  1059. // Exit if there is nothing to do.
  1060. if (empty($responses)) {
  1061. return FALSE;
  1062. }
  1063. // If the t function is not available use httprl_pr.
  1064. $t = function_exists('t') ? 't' : 'httprl_pr';
  1065. // Remove errors from responses array and set the global timeout.
  1066. $global_timeout = 1;
  1067. $global_connection_limit = 1;
  1068. $stall_freads = FALSE;
  1069. foreach ($responses as $id => &$result) {
  1070. if (!empty($result->error)) {
  1071. $result->status = 'Connection not made.';
  1072. // Do post processing on the stream.
  1073. httprl_post_processing($id, $responses, $output);
  1074. continue;
  1075. }
  1076. // Get connection limits.
  1077. $global_connection_limit = max($global_connection_limit, $result->options['global_connections']);
  1078. if (!isset($domain_connection_limit[$result->options['headers']['Host']])) {
  1079. $domain_connection_limit[$result->options['headers']['Host']] = max(1, $result->options['domain_connections']);
  1080. }
  1081. else {
  1082. $domain_connection_limit[$result->options['headers']['Host']] = max($domain_connection_limit[$result->options['headers']['Host']], $result->options['domain_connections']);
  1083. }
  1084. // Set global timeout.
  1085. $global_timeout = max($global_timeout, $result->options['global_timeout']);
  1086. // Issue fwrite, return. Run fread later on in the script.
  1087. if (!empty($result->options['stall_fread']) && !$static_stall_freads) {
  1088. $static_stall_freads = TRUE;
  1089. $stall_freads = TRUE;
  1090. }
  1091. }
  1092. // Record start time.
  1093. $start_time_this_run = $start_time_global = microtime(TRUE);
  1094. // Record the number of db pings done.
  1095. $ping_db_counts = array();
  1096. $full_bootstrap = httprl_drupal_full_bootstrap();
  1097. // Run the loop as long as we have a stream to read/write to.
  1098. $stream_select_timeout = 1;
  1099. $stream_write_count = 0;
  1100. while (!empty($responses)) {
  1101. // Initialize connection limits.
  1102. $this_run = array();
  1103. $global_connection_count = 0;
  1104. $domain_connection_count = array();
  1105. $restart_timers = FALSE;
  1106. // Get time.
  1107. $now = microtime(TRUE);
  1108. // Calculate times.
  1109. $elapsed_time = $now - $start_time_this_run;
  1110. $start_time_this_run = $now;
  1111. $global_time = $global_timeout - ($start_time_this_run - $start_time_global);
  1112. // See if the DB needs to be pinged.
  1113. $rounded_time = floor($elapsed_time);
  1114. if ( $full_bootstrap
  1115. && !empty($result->options['ping_db'])
  1116. && $rounded_time >= $result->options['ping_db']
  1117. && $rounded_time % $result->options['ping_db'] == 0
  1118. && empty($ping_db_counts[$rounded_time])
  1119. ) {
  1120. $empty_array = array();
  1121. system_get_files_database($empty_array, 'ping_db');
  1122. $ping_db_counts[$rounded_time] = 1;
  1123. }
  1124. // Inspect each stream, checking for timeouts and connection limits.
  1125. foreach ($responses as $id => &$result) {
  1126. // See if function timed out.
  1127. if ($global_time <= 0) {
  1128. // Function timed out & the request is not done.
  1129. if ($result->status == 'Connecting.') {
  1130. $result->error = $t('Function timed out. TCP.');
  1131. // If stream is not done writing, then remove one from the write count.
  1132. if (isset($result->fp)) {
  1133. $stream_write_count--;
  1134. }
  1135. }
  1136. elseif ($result->status == 'Writing to server.') {
  1137. $result->error = $t('Function timed out. Write.');
  1138. // If stream is not done writing, then remove one from the write count.
  1139. if (isset($result->fp)) {
  1140. $stream_write_count--;
  1141. }
  1142. }
  1143. else {
  1144. $result->error = $t('Function timed out. Read');
  1145. }
  1146. $result->code = HTTPRL_FUNCTION_TIMEOUT;
  1147. $result->status = 'Done.';
  1148. // Do post processing on the stream and close it.
  1149. httprl_post_processing($id, $responses, $output, $global_time);
  1150. continue;
  1151. }
  1152. // Do not calculate local timeout if a file pointer doesn't exist.
  1153. if (isset($result->fp)) {
  1154. // Add the elapsed time to this stream.
  1155. $result->running_time += $elapsed_time;
  1156. // Calculate how much time is left of the original timeout value.
  1157. $timeout = $result->options['timeout'] - $result->running_time;
  1158. // Connection was dropped or connection timed out.
  1159. if ($timeout <= 0) {
  1160. $result->error = $t('Connection timed out.');
  1161. // Stream timed out & the request is not done.
  1162. if ($result->status == 'Writing to server.') {
  1163. $result->error .= ' ' . $t('Write.');
  1164. // If stream is not done writing, then remove one from the write count.
  1165. $stream_write_count--;
  1166. }
  1167. else {
  1168. $result->error .= ' ' . $t('Read.');
  1169. }
  1170. $result->code = HTTPRL_REQUEST_TIMEOUT;
  1171. $result->status = 'Done.';
  1172. // Do post processing on the stream.
  1173. httprl_post_processing($id, $responses, $output, $timeout);
  1174. continue;
  1175. }
  1176. // Connection was dropped or connection timed out.
  1177. if ($result->status == 'Connecting.' && $result->running_time > $result->options['connect_timeout']) {
  1178. $socket_name = stream_socket_get_name($result->fp, TRUE);
  1179. if (empty($socket_name) || $result->running_time > ($result->options['connect_timeout'] * 1.5)) {
  1180. $result->error = $t('Connection timed out.');
  1181. // Stream timed out & the request is not done.
  1182. if ($result->status == 'Connecting.') {
  1183. $result->error .= ' ' . $t('TCP Connect Timeout.');
  1184. // If stream is not done writing, then remove one from the write count.
  1185. $stream_write_count--;
  1186. }
  1187. $result->code = HTTPRL_REQUEST_TIMEOUT;
  1188. $result->status = 'Done.';
  1189. // Do post processing on the stream.
  1190. httprl_post_processing($id, $responses, $output, $timeout);
  1191. continue;
  1192. }
  1193. }
  1194. if (!isset($responses[$id]->time_to_first_byte) && $result->running_time > $result->options['ttfb_timeout']) {
  1195. $result->error = $t('Connection timed out. Time to First Byte Timeout.');
  1196. $result->code = HTTPRL_REQUEST_ABORTED;
  1197. $result->status = 'Done.';
  1198. // Do post processing on the stream.
  1199. httprl_post_processing($id, $responses, $output, $timeout);
  1200. continue;
  1201. }
  1202. }
  1203. // Connection was handled elsewhere.
  1204. if (!isset($result->fp) && $result->status != 'Connecting.') {
  1205. // Do post processing on the stream.
  1206. httprl_post_processing($id, $responses, $output);
  1207. continue;
  1208. }
  1209. // Set the connection limits for this run.
  1210. // Get the host name.
  1211. $host = $result->options['headers']['Host'];
  1212. // Set the domain connection limit if none has been defined yet.
  1213. if (!isset($domain_connection_limit[$host])) {
  1214. $domain_connection_limit[$host] = max(1, $result->options['domain_connections']);
  1215. }
  1216. // Count up the number of connections.
  1217. $global_connection_count++;
  1218. if (empty($domain_connection_count[$host])) {
  1219. $domain_connection_count[$host] = 1;
  1220. }
  1221. else {
  1222. $domain_connection_count[$host]++;
  1223. }
  1224. // If the conditions are correct, let the stream be ran in this loop.
  1225. if ($global_connection_limit >= $global_connection_count && $domain_connection_limit[$host] >= $domain_connection_count[$host]) {
  1226. // Establish a new connection.
  1227. if (!isset($result->fp) && $result->status == 'Connecting.') {
  1228. // Establish a connection to the server.
  1229. httprl_establish_stream_connection($result);
  1230. // Reset timer.
  1231. $restart_timers = TRUE;
  1232. // Get lock if needed.
  1233. if (!empty($result->options['lock_name'])) {
  1234. httprl_acquire_lock($result);
  1235. }
  1236. // If connection can not be established bail out here.
  1237. if (!$result->fp) {
  1238. // Do post processing on the stream.
  1239. httprl_post_processing($id, $responses, $output);
  1240. $domain_connection_count[$host]--;
  1241. $global_connection_count--;
  1242. continue;
  1243. }
  1244. $stream_write_count++;
  1245. }
  1246. if (!empty($result->fp)) {
  1247. $this_run[$id] = $result->fp;
  1248. }
  1249. }
  1250. }
  1251. // All streams removed; exit loop.
  1252. if (empty($responses)) {
  1253. break;
  1254. }
  1255. // Restart timers.
  1256. if ($restart_timers) {
  1257. $start_time_this_run = microtime(TRUE);
  1258. }
  1259. // No streams selected; restart loop from the top.
  1260. if (empty($this_run)) {
  1261. continue;
  1262. }
  1263. // Set the read and write vars to the streams var.
  1264. $read = $write = $this_run;
  1265. $except = array();
  1266. // Do some voodoo and open all streams at once. Wait 25ms for streams to
  1267. // respond.
  1268. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  1269. // If on windows, use error suppression http://drupal.org/node/1869026
  1270. $n = @stream_select($read, $write, $except, $stream_select_timeout, 25000);
  1271. }
  1272. else {
  1273. $n = stream_select($read, $write, $except, $stream_select_timeout, 25000);
  1274. }
  1275. $stream_select_timeout = 0;
  1276. // An error occurred with the streams. Remove bad ones.
  1277. if ($n === FALSE) {
  1278. $merged = array_unique(array_merge($read, $write, $except));
  1279. foreach ($merged as $m) {
  1280. $id = array_search($m, $this_run);
  1281. @fclose($m);
  1282. if ($id !== FALSE && isset($responses[$id])) {
  1283. watchdog('httprl', 'The following url had a stream_select error and had to be terminated: %info', array('%info' => $responses[$id]->url), WATCHDOG_ERROR);
  1284. unset($responses[$id]);
  1285. }
  1286. }
  1287. }
  1288. // We have some streams to read/write to.
  1289. $rw_done = FALSE;
  1290. if (!empty($n)) {
  1291. if (!empty($read) && is_array($read)) {
  1292. // Readable sockets either have data for us, or are failed connection
  1293. // attempts.
  1294. foreach ($read as $r) {
  1295. $id = array_search($r, $this_run);
  1296. // Make sure ID is in the streams.
  1297. if ($id === FALSE) {
  1298. @fclose($r);
  1299. continue;
  1300. }
  1301. // Do not read from the non blocking sockets.
  1302. if (empty($responses[$id]->options['blocking'])) {
  1303. // Do post processing on the stream and close it.
  1304. httprl_post_processing($id, $responses, $output);
  1305. continue;
  1306. }
  1307. // Read socket.
  1308. $chunk = fread($r, $responses[$id]->chunk_size);
  1309. if (strlen($chunk) > 0) {
  1310. $rw_done = TRUE;
  1311. if (!isset($responses[$id]->time_to_first_byte)) {
  1312. // Calculate Time to First Byte.
  1313. $responses[$id]->time_to_first_byte = $result->running_time + microtime(TRUE) - $start_time_this_run;
  1314. }
  1315. }
  1316. $responses[$id]->data .= $chunk;
  1317. // Process the headers if we have some data.
  1318. if (!empty($responses[$id]->data) && empty($responses[$id]->headers) &&
  1319. ( strpos($responses[$id]->data, "\r\n\r\n")
  1320. || strpos($responses[$id]->data, "\n\n")
  1321. || strpos($responses[$id]->data, "\r\r")
  1322. )
  1323. ) {
  1324. // See if the headers are in the data stream.
  1325. httprl_parse_data($responses[$id]);
  1326. if (!empty($responses[$id]->headers)) {
  1327. // Stream was a redirect, kill & close this connection; redirect is
  1328. // being followed now.
  1329. if (!empty($responses[$id]->options['internal_states']['kill'])) {
  1330. fclose($r);
  1331. unset($responses[$id]);
  1332. continue;
  1333. }
  1334. // Now that we have the headers, increase the chunk size.
  1335. $responses[$id]->chunk_size = $responses[$id]->options['chunk_size_read'];
  1336. // If a range header is set, 200 was returned, method is GET,
  1337. // calculate how many bytes need to be downloaded.
  1338. if ( !empty($responses[$id]->options['headers']['Range'])
  1339. && $responses[$id]->code == 200
  1340. && $responses[$id]->options['method'] == 'GET'
  1341. ) {
  1342. $responses[$id]->ranges = httprl_get_ranges($responses[$id]->options['headers']['Range']);
  1343. $responses[$id]->options['max_data_size'] = httprl_get_last_byte_from_range($responses[$id]->ranges);
  1344. }
  1345. }
  1346. }
  1347. // Close the connection if Transfer-Encoding & Content-Encoding are not
  1348. // used, a Range request was made and the currently downloaded data size
  1349. // is larger than the Range request.
  1350. if ( !empty($responses[$id]->options['max_data_size'])
  1351. && is_numeric($responses[$id]->options['max_data_size'])
  1352. && (!isset($result->headers['transfer-encoding']) || $result->headers['transfer-encoding'] != 'chunked')
  1353. && (!isset($result->headers['content-encoding']) || ($result->headers['content-encoding'] != 'gzip' && $result->headers['content-encoding'] != 'deflate'))
  1354. && $responses[$id]->options['max_data_size'] < httprl_strlen($responses[$id]->data)
  1355. ) {
  1356. $responses[$id]->status = 'Done.';
  1357. // Do post processing on the stream.
  1358. httprl_post_processing($id, $responses, $output);
  1359. continue;
  1360. }
  1361. // Get stream data.
  1362. $info = stream_get_meta_data($r);
  1363. $alive = !$info['eof'] && !feof($r) && !$info['timed_out'] && strlen($chunk);
  1364. if (!$alive) {
  1365. if ($responses[$id]->status == 'Connecting.') {
  1366. $responses[$id]->error = $t('Connection refused by destination. TCP.');
  1367. $responses[$id]->code = HTTPRL_CONNECTION_REFUSED;
  1368. }
  1369. if ($responses[$id]->status == 'Writing to server.') {
  1370. $responses[$id]->error = $t('Connection refused by destination. Write.');
  1371. $responses[$id]->code = HTTPRL_CONNECTION_REFUSED;
  1372. }
  1373. $responses[$id]->status = 'Done.';
  1374. // Do post processing on the stream.
  1375. httprl_post_processing($id, $responses, $output);
  1376. continue;
  1377. }
  1378. else {
  1379. $responses[$id]->status = 'Reading data';
  1380. }
  1381. }
  1382. }
  1383. // Write to each stream if it is available.
  1384. if ($stream_write_count > 0 && !empty($write) && is_array($write)) {
  1385. foreach ($write as $w) {
  1386. $id = array_search($w, $this_run);
  1387. // Make sure ID is in the streams & status is for writing.
  1388. if ($id === FALSE || empty($responses[$id]->status) || ($responses[$id]->status != 'Connecting.' && $responses[$id]->status != 'Writing to server.')) {
  1389. continue;
  1390. }
  1391. // Keep track of how many bytes are sent.
  1392. if (!isset($responses[$id]->bytes_sent)) {
  1393. $responses[$id]->bytes_sent = 0;
  1394. }
  1395. // Have twice the number of bytes available for fwrite.
  1396. $data_to_send = substr($responses[$id]->request, $responses[$id]->bytes_sent, 2 * $responses[$id]->options['chunk_size_write']);
  1397. // Calculate the number of bytes we need to write to the stream.
  1398. $len = httprl_strlen($data_to_send);
  1399. if ($len > 0) {
  1400. // Write to the stream.
  1401. $bytes = fwrite($w, $data_to_send, min($responses[$id]->options['chunk_size_write'], $len));
  1402. }
  1403. else {
  1404. // Nothing to write.
  1405. $bytes = $len;
  1406. }
  1407. // See if we are done with writing.
  1408. if ($bytes === FALSE) {
  1409. // fwrite failed.
  1410. $responses[$id]->error = $t('fwrite() failed.');
  1411. $responses[$id]->code = HTTPRL_REQUEST_FWRITE_FAIL;
  1412. $responses[$id]->status = 'Done.';
  1413. $stream_write_count--;
  1414. // Do post processing on the stream.
  1415. httprl_post_processing($id, $responses, $output);
  1416. continue;
  1417. }
  1418. elseif ($bytes >= $len) {
  1419. // fwrite is done.
  1420. $stream_write_count--;
  1421. // If this is a non blocking request then close the connection and
  1422. // destroy the stream.
  1423. if (empty($responses[$id]->options['blocking'])) {
  1424. $responses[$id]->status = 'Non-Blocking request sent out. Not waiting for the response.';
  1425. // Do post processing on the stream.
  1426. httprl_post_processing($id, $responses, $output);
  1427. continue;
  1428. }
  1429. else {
  1430. // All data has been written to the socket. We are read only from
  1431. // here on out.
  1432. $responses[$id]->status = "Request sent, waiting for response.";
  1433. }
  1434. // Record how many bytes were sent.
  1435. $responses[$id]->bytes_sent += $bytes;
  1436. $rw_done = TRUE;
  1437. }
  1438. else {
  1439. // Change status to 'Writing to server.'
  1440. if ($responses[$id]->status = 'Connecting.') {
  1441. $responses[$id]->status = 'Writing to server.';
  1442. }
  1443. // There is more data to write to this socket. Cut what was sent
  1444. // across the stream and resend whats left next time in the loop.
  1445. $responses[$id]->bytes_sent += $bytes;
  1446. $rw_done = TRUE;
  1447. }
  1448. }
  1449. }
  1450. elseif ($stall_freads) {
  1451. return;
  1452. }
  1453. }
  1454. if (!$rw_done) {
  1455. // Wait 5ms for data buffers.
  1456. usleep(5000);
  1457. }
  1458. }
  1459. // Copy output.
  1460. $return = $output;
  1461. // Free memory/reset static variables.
  1462. $responses = array();
  1463. $counter = 0;
  1464. $output = array();
  1465. $static_stall_freads = FALSE;
  1466. return $return;
  1467. }
  1468. /**
  1469. * Extract the header and meta data from the http data stream.
  1470. *
  1471. * @see drupal_http_request()
  1472. *
  1473. * @todo Send cookies in the redirect request if domain/path match.
  1474. *
  1475. * @param object $result
  1476. * An object from httprl_send_request.
  1477. */
  1478. function httprl_parse_data(&$result) {
  1479. // If in non blocking mode, skip.
  1480. if (empty($result->options['blocking'])) {
  1481. return;
  1482. }
  1483. // If the headers are already parsed, skip.
  1484. if (!empty($result->headers)) {
  1485. return;
  1486. }
  1487. // If the t function is not available use httprl_pr.
  1488. $t = function_exists('t') ? 't' : 'httprl_pr';
  1489. // Parse response headers from the response body.
  1490. // Be tolerant of malformed HTTP responses that separate header and body with
  1491. // \n\n or \r\r instead of \r\n\r\n.
  1492. $response = $result->data;
  1493. list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
  1494. $response = preg_split("/\r\n|\n|\r/", $response);
  1495. // Parse the response status line.
  1496. $protocol_code_array = explode(' ', trim(array_shift($response)), 3);
  1497. $result->protocol = $protocol_code_array[0];
  1498. $code = (int) $protocol_code_array[1];
  1499. // If the response does not include a description, don't try to process it.
  1500. $result->status_message = isset($protocol_code_array[2]) ? $protocol_code_array[2] : '';
  1501. unset($protocol_code_array);
  1502. $result->headers = array();
  1503. // Parse the response headers.
  1504. $cookie_primary_counter = 0;
  1505. while ($line = trim(array_shift($response))) {
  1506. list($name, $value) = explode(':', $line, 2);
  1507. $name = strtolower($name);
  1508. // Parse cookies before they get added to the header.
  1509. if ($name == 'set-cookie') {
  1510. // Extract the key value pairs for this cookie.
  1511. foreach (explode(';', $value) as $cookie_name_value) {
  1512. $temp = explode('=', trim($cookie_name_value));
  1513. $cookie_key = trim($temp[0]);
  1514. $cookie_value = isset($temp[1]) ? trim($temp[1]) : '';
  1515. unset($temp);
  1516. // The cookie name-value pair always comes first (RFC 2109 4.2.2).
  1517. if (!isset($result->cookies[$cookie_primary_counter])) {
  1518. $result->cookies[$cookie_primary_counter] = array(
  1519. 'name' => $cookie_key,
  1520. 'value' => $cookie_value,
  1521. );
  1522. }
  1523. // Extract the rest of the attribute-value pairs.
  1524. else {
  1525. $result->cookies[$cookie_primary_counter] += array(
  1526. $cookie_key => $cookie_value,
  1527. );
  1528. }
  1529. }
  1530. $cookie_primary_counter++;
  1531. }
  1532. // Add key value pairs to the header; including cookies.
  1533. if (isset($result->headers[$name]) && $name == 'set-cookie') {
  1534. // RFC 2109: the Set-Cookie response header comprises the token Set-
  1535. // Cookie:, followed by a comma-separated list of one or more cookies.
  1536. $result->headers[$name] .= ',' . trim($value);
  1537. }
  1538. else {
  1539. $result->headers[$name] = trim($value);
  1540. }
  1541. }
  1542. $responses = array(
  1543. 100 => 'Continue',
  1544. 101 => 'Switching Protocols',
  1545. 200 => 'OK',
  1546. 201 => 'Created',
  1547. 202 => 'Accepted',
  1548. 203 => 'Non-Authoritative Information',
  1549. 204 => 'No Content',
  1550. 205 => 'Reset Content',
  1551. 206 => 'Partial Content',
  1552. 300 => 'Multiple Choices',
  1553. 301 => 'Moved Permanently',
  1554. 302 => 'Found',
  1555. 303 => 'See Other',
  1556. 304 => 'Not Modified',
  1557. 305 => 'Use Proxy',
  1558. 307 => 'Temporary Redirect',
  1559. 400 => 'Bad Request',
  1560. 401 => 'Unauthorized',
  1561. 402 => 'Payment Required',
  1562. 403 => 'Forbidden',
  1563. 404 => 'Not Found',
  1564. 405 => 'Method Not Allowed',
  1565. 406 => 'Not Acceptable',
  1566. 407 => 'Proxy Authentication Required',
  1567. 408 => 'Request Time-out',
  1568. 409 => 'Conflict',
  1569. 410 => 'Gone',
  1570. 411 => 'Length Required',
  1571. 412 => 'Precondition Failed',
  1572. 413 => 'Request Entity Too Large',
  1573. 414 => 'Request-URI Too Large',
  1574. 415 => 'Unsupported Media Type',
  1575. 416 => 'Requested range not satisfiable',
  1576. 417 => 'Expectation Failed',
  1577. 500 => 'Internal Server Error',
  1578. 501 => 'Not Implemented',
  1579. 502 => 'Bad Gateway',
  1580. 503 => 'Service Unavailable',
  1581. 504 => 'Gateway Time-out',
  1582. 505 => 'HTTP Version not supported',
  1583. );
  1584. // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  1585. // base code in their class.
  1586. if (!isset($responses[$code])) {
  1587. $code = floor($code / 100) * 100;
  1588. }
  1589. $result->code = $code;
  1590. switch ($code) {
  1591. case 200: // OK
  1592. case 201: // Created
  1593. case 202: // Accepted
  1594. case 206: // Partial Content
  1595. case 304: // Not modified
  1596. break;
  1597. case 301: // Moved permanently
  1598. case 302: // Moved temporarily
  1599. case 307: // Moved temporarily
  1600. $location = @parse_url($result->headers['location']);
  1601. // If location isn't fully qualified URL (as per W3 RFC2616), build one.
  1602. if (empty($location['scheme']) || empty($location['host'])) {
  1603. // Get the important parts from the original request.
  1604. $original_location = @parse_url($result->url);
  1605. // Assume request is to self if none of this was setup correctly.
  1606. $location['scheme'] = !empty($location['scheme']) ? $location['scheme'] : $original_location['scheme'];
  1607. $location['host'] = !empty($location['host']) ? $location['host'] : !empty($original_location['host']) ? $original_location['host'] : $_SERVER['HTTP_HOST'];
  1608. $location['port'] = !empty($location['port']) ? $location['port'] : !empty($original_location['port']) ? $original_location['port'] : '';
  1609. $location = httprl_glue_url($location);
  1610. }
  1611. else {
  1612. $location = $result->headers['location'];
  1613. }
  1614. // Set internal redirect states.
  1615. $result->options['internal_states']['redirect_code_array'][] = $code;
  1616. $result->options['internal_states']['redirect_url_array'][] = $location;
  1617. if (!isset($result->options['internal_states']['original_url'])) {
  1618. $result->options['internal_states']['original_url'] = $result->url;
  1619. }
  1620. // Error out if we hit the max redirect.
  1621. if ($result->options['max_redirects'] <= 0) {
  1622. $result->code = HTTPRL_REQUEST_ALLOWED_REDIRECTS_EXHAUSTED;
  1623. $result->error = $t('Maximum allowed redirects exhausted.');
  1624. }
  1625. else {
  1626. // Redirect to the new location.
  1627. // TODO: Send cookies in the redirect request if domain/path match.
  1628. $result->options['max_redirects']--;
  1629. if (isset($result->options['headers']['Referer'])) {
  1630. $result->options['headers']['Referer'] = $result->url;
  1631. }
  1632. // Remove the host from the header.
  1633. unset($result->options['headers']['Host']);
  1634. // Pass along running time.
  1635. $result->options['internal_states']['running_time'] = $result->running_time;
  1636. // Send new request.
  1637. httprl_request($location, $result->options);
  1638. // Kill this request.
  1639. $result->options['internal_states']['kill'] = TRUE;
  1640. }
  1641. break;
  1642. default:
  1643. $result->error = $result->status_message;
  1644. }
  1645. }
  1646. /**
  1647. * Parse a range header into start and end byte ranges.
  1648. *
  1649. * @param string $input
  1650. * String in the form of bytes=0-1024 or bytes=0-1024,2048-4096
  1651. *
  1652. * @return array
  1653. * Keyed arrays containing start and end values for the byte ranges.
  1654. * Empty array if the string can not be parsed.
  1655. */
  1656. function httprl_get_ranges($input) {
  1657. $ranges = array();
  1658. // Make sure the input string matches the correct format.
  1659. $string = preg_match('/^bytes=((\d*-\d*,? ?)+)$/', $input, $matches) ? $matches[1] : FALSE;
  1660. if (!empty($string)) {
  1661. // Handle multiple ranges.
  1662. foreach (explode(',', $string) as $range) {
  1663. // Get the start and end byte values for this range.
  1664. $values = explode('-', $range);
  1665. if (count($values) != 2) {
  1666. return FALSE;
  1667. }
  1668. $ranges[] = array('start' => $values[0], 'end' => $values[1]);
  1669. }
  1670. }
  1671. return $ranges;
  1672. }
  1673. /**
  1674. * Given an array of ranges, get the last byte we need to download.
  1675. *
  1676. * @param array $ranges
  1677. * Multi dimensional array
  1678. *
  1679. * @return int|null
  1680. * NULL: Get all values; int: last byte to download.
  1681. */
  1682. function httprl_get_last_byte_from_range($ranges) {
  1683. $max = 0;
  1684. if (empty($ranges)) {
  1685. return NULL;
  1686. }
  1687. foreach ($ranges as $range) {
  1688. if (!is_numeric($range['start']) || !is_numeric($range['end'])) {
  1689. return NULL;
  1690. }
  1691. $max = max($range['end'] + 1, $max);
  1692. }
  1693. return $max;
  1694. }
  1695. /**
  1696. * Run post processing on the request if we are done reading.
  1697. *
  1698. * Decode transfer-encoding and content-encoding.
  1699. * Reconstruct the internal redirect arrays.
  1700. *
  1701. * @result object
  1702. * An object from httprl_send_request.
  1703. */
  1704. function httprl_post_processing($id, &$responses, &$output, $time_left = NULL) {
  1705. // Create the result reference.
  1706. $result = &$responses[$id];
  1707. // Close file.
  1708. if (isset($result->fp)) {
  1709. @fclose($result->fp);
  1710. }
  1711. // Set timeout.
  1712. if (is_null($time_left)) {
  1713. $time_left = $result->options['timeout'] - $result->running_time;
  1714. }
  1715. $result->options['timeout'] = $time_left;
  1716. // Assemble redirects.
  1717. httprl_reconstruct_redirects($result);
  1718. // Decode chunked transfer-encoding and gzip/deflate content-encoding.
  1719. httprl_decode_data($result);
  1720. // If this is a background callback request, extract the data and return.
  1721. if (isset($result->options['internal_states']) && array_key_exists('background_function_return', $result->options['internal_states']) && isset($result->headers['content-type']) && strpos($result->headers['content-type'], 'application/x-www-form-urlencoded') !== FALSE) {
  1722. httprl_extract_background_callback_data($result);
  1723. unset($responses[$id]);
  1724. return;
  1725. }
  1726. // See if a full bootstrap has been done.
  1727. $full_bootstrap = httprl_drupal_full_bootstrap();
  1728. // Allow a user defined function to alter all $responses.
  1729. if ($full_bootstrap && !empty($result->options['alter_all_streams_function']) && function_exists($result->options['alter_all_streams_function'])) {
  1730. $result->options['alter_all_streams_function']($id, $responses);
  1731. }
  1732. unset($responses[$id]);
  1733. // Allow other modules to alter the result.
  1734. if ($full_bootstrap) {
  1735. // Call hook_httprl_post_processing_alter().
  1736. drupal_alter('httprl_post_processing', $result);
  1737. }
  1738. // Run callback so other modules can do stuff in the event loop.
  1739. if ( $full_bootstrap
  1740. && !empty($result->options['callback'])
  1741. && is_array($result->options['callback'])
  1742. && !empty($result->options['callback'][0])
  1743. && is_array($result->options['callback'][0])
  1744. && !empty($result->options['callback'][0]['function'])
  1745. ) {
  1746. httprl_run_callback($result);
  1747. }
  1748. // Run background_callback.
  1749. if ( !empty($result->options['background_callback'])
  1750. && is_array($result->options['background_callback'])
  1751. && !empty($result->options['background_callback'][0])
  1752. && is_array($result->options['background_callback'][0])
  1753. && !empty($result->options['background_callback'][0]['function'])
  1754. ) {
  1755. $call_is_queued = httprl_queue_background_callback($result->options['background_callback'], $result);
  1756. if (is_null($call_is_queued)) {
  1757. watchdog('httprl', 'Background callback attempted but it is disabled. Going to use a normal callback');
  1758. unset($result->options['callback']);
  1759. $result->options['callback'] = $result->options['background_callback'];
  1760. unset($result->options['background_callback']);
  1761. httprl_run_callback($result);
  1762. }
  1763. }
  1764. // Copy the result to the output array.
  1765. if (isset($result->url)) {
  1766. $output[$result->url] = $result;
  1767. }
  1768. }
  1769. /**
  1770. * Extract background callback data.
  1771. *
  1772. * Set the return and printed values & any pass by reference values from a
  1773. * background callback operation.
  1774. *
  1775. * @param object $result
  1776. * An object from httprl_send_request.
  1777. */
  1778. function httprl_extract_background_callback_data(&$result) {
  1779. // Extract data from string.
  1780. $data = array();
  1781. parse_str($result->data, $data);
  1782. // Follow rfc4648 for base64url
  1783. // @see http://tools.ietf.org/html/rfc4648#page-7
  1784. $data = unserialize(base64_decode(strtr(current($data), array('-' => '+', '_' => '/'))));
  1785. // Set return and printed values.
  1786. if (isset($data['return'])) {
  1787. $result->options['internal_states']['background_function_return'] = $data['return'];
  1788. }
  1789. if (isset($data['printed'])) {
  1790. $result->options['internal_states']['background_function_printed'] = $data['printed'];
  1791. }
  1792. // Set any pass by reference values.
  1793. if (isset($data['args'])) {
  1794. httprl_recursive_array_reference_extract($result->options['internal_states']['background_function_args'], $data['args']);
  1795. }
  1796. }
  1797. /**
  1798. * Replace data in place so pass by reference sill works.
  1799. *
  1800. * @param array $array
  1801. * An array containing the references if any.
  1802. * @param array $data
  1803. * An array that has the new values to copy into $array.
  1804. * @param int $depth
  1805. * Only go 10 levels deep. Prevent infinite loops.
  1806. */
  1807. function httprl_recursive_array_reference_extract(&$array, $data, $depth = 0) {
  1808. $depth++;
  1809. foreach ($array as $key => &$value) {
  1810. if (isset($data[$key])) {
  1811. if (is_array($data[$key]) && is_array($value) && $depth < 10) {
  1812. $value = httprl_recursive_array_reference_extract($value, $data[$key], $depth);
  1813. }
  1814. else {
  1815. $value = $data[$key];
  1816. }
  1817. }
  1818. else {
  1819. $value = NULL;
  1820. }
  1821. }
  1822. // Copy new keys into the data structure.
  1823. foreach ($data as $key => $value) {
  1824. if (isset($array[$key])) {
  1825. continue;
  1826. }
  1827. $array[$key] = $value;
  1828. }
  1829. }
  1830. /**
  1831. * Run callback.
  1832. *
  1833. * Will run the given callback returning values and what might have been
  1834. * printed by that function, as well as respecting any pass by reference values.
  1835. *
  1836. * @param object $result
  1837. * An object from httprl_send_request.
  1838. */
  1839. function httprl_run_callback(&$result) {
  1840. // Get options.
  1841. $callback_options = $result->options['callback'][0];
  1842. // Merge in values by reference.
  1843. $result->options['callback'][0] = &$result;
  1844. // Capture anything printed out.
  1845. if (array_key_exists('printed', $callback_options)) {
  1846. ob_start();
  1847. }
  1848. // Call function.
  1849. $callback_options['return'] = call_user_func_array($callback_options['function'], $result->options['callback']);
  1850. if (array_key_exists('printed', $callback_options)) {
  1851. $callback_options['printed'] = ob_get_contents();
  1852. ob_end_clean();
  1853. }
  1854. // Add options back into the callback array.
  1855. if (isset($result->options['callback'])) {
  1856. array_unshift($result->options['callback'], $callback_options);
  1857. }
  1858. }
  1859. /**
  1860. * Run callback in the background.
  1861. *
  1862. * Will run the given callback returning values and what might have been
  1863. * printed by that function, as well as respecting any pass by reference values.
  1864. *
  1865. * @param array $args
  1866. * An array of arguments, first key value pair is used to control the
  1867. * callback function. The rest of the key value pairs will be arguments for
  1868. * the callback function.
  1869. * @param object $result
  1870. * (optional) An object from httprl_send_request. If this is set, this will
  1871. * be the first argument of the function.
  1872. */
  1873. function httprl_queue_background_callback(&$args, &$result = NULL) {
  1874. // Use a counter to prevent key collisions in httprl_send_request.
  1875. static $counter;
  1876. if (!isset($counter)) {
  1877. $counter = 0;
  1878. }
  1879. $counter++;
  1880. if (!httprl_is_background_callback_capable()) {
  1881. return NULL;
  1882. }
  1883. // Get URL to call function in background.
  1884. if (empty($callback_options['url'])) {
  1885. $url = httprl_build_url_self('httprl_async_function_callback?count=' . $counter);
  1886. }
  1887. else {
  1888. $url = $callback_options['url'];
  1889. }
  1890. // Get options.
  1891. $callback_options = $args[0];
  1892. if (is_null($result)) {
  1893. array_shift($args);
  1894. }
  1895. else {
  1896. // Merge in this request by reference.
  1897. $args[0] = &$result;
  1898. }
  1899. // Set blocking mode.
  1900. if (isset($callback_options['return']) || isset($callback_options['printed'])) {
  1901. $mode = TRUE;
  1902. }
  1903. else {
  1904. $mode = FALSE;
  1905. }
  1906. // Make sure some array keys exist.
  1907. if (!isset($callback_options['return'])) {
  1908. $callback_options['return'] = '';
  1909. }
  1910. if (!isset($callback_options['function'])) {
  1911. $callback_options['function'] = '';
  1912. }
  1913. // Get the maximum amount of time this could take.
  1914. $times = array(
  1915. httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT),
  1916. httprl_variable_get('httprl_global_timeout', HTTPRL_GLOBAL_TIMEOUT),
  1917. );
  1918. if (isset($callback_options['options']['timeout'])) {
  1919. $times[] = $callback_options['options']['timeout'];
  1920. }
  1921. if (isset($callback_options['options']['global_timeout'])) {
  1922. $times[] = $callback_options['options']['global_timeout'];
  1923. }
  1924. // Create lock name for this run.
  1925. $available = FALSE;
  1926. $lock_counter = 0;
  1927. while (!$available && $lock_counter < 20) {
  1928. // 512 bits = 64 bytes.
  1929. if (function_exists('drupal_random_bytes')) {
  1930. $name = 'httprl_' . hash('sha512', drupal_random_bytes(64));
  1931. }
  1932. elseif (function_exists('openssl_random_pseudo_bytes')) {
  1933. $name = 'httprl_' . hash('sha512', openssl_random_pseudo_bytes(64));
  1934. }
  1935. else {
  1936. $name = 'httprl_' . hash('sha512', mt_rand() . microtime(TRUE) . serialize($_SERVER));
  1937. }
  1938. $available = lock_may_be_available($name);
  1939. $lock_counter++;
  1940. }
  1941. $callback_options['options']['lock_name'] = $name;
  1942. // Create data array and options for request.
  1943. $options = array(
  1944. 'data' => array(
  1945. 'master_key' => hash('sha512', httprl_drupal_get_private_key()),
  1946. 'temp_key' => $name,
  1947. 'mode' => $mode,
  1948. 'php_timeout' => max($times),
  1949. 'function' => $callback_options['function'],
  1950. 'context' => isset($callback_options['context']) ? $callback_options['context'] : array(),
  1951. // Follow rfc4648 for base64url
  1952. // @see http://tools.ietf.org/html/rfc4648#page-7
  1953. 'args' => strtr(base64_encode(serialize($args)), array('+' => '-', '/' => '_')),
  1954. ),
  1955. 'internal_states' => array(
  1956. 'background_function_return' => &$callback_options['return'],
  1957. 'background_function_args' => &$args,
  1958. ),
  1959. 'blocking' => $mode,
  1960. 'method' => 'POST',
  1961. );
  1962. if (isset($callback_options['printed'])) {
  1963. $options['internal_states']['background_function_printed'] = &$callback_options['printed'];
  1964. }
  1965. if (isset($callback_options['options']) && is_array($callback_options['options'])) {
  1966. $options += $callback_options['options'];
  1967. }
  1968. // Set Host header.
  1969. if (empty($options['headers']['Host']) && !empty($_SERVER['HTTP_HOST'])) {
  1970. $options['headers']['Host'] = $_SERVER['HTTP_HOST'];
  1971. }
  1972. // Set Session header if requested to.
  1973. if (!empty($callback_options['context']['session']) && !empty($_COOKIE[session_name()])) {
  1974. if (!isset($options['headers']['Cookie'])) {
  1975. $options['headers']['Cookie'] = '';
  1976. }
  1977. $options['headers']['Cookie'] = session_name() . '=' . $_COOKIE[session_name()] . ';';
  1978. }
  1979. // Send Request.
  1980. return httprl_request($url, $options);
  1981. }
  1982. /**
  1983. * Get a lock so background calls work.
  1984. *
  1985. * @param object $result
  1986. * An object from httprl_send_request.
  1987. */
  1988. function httprl_acquire_lock(&$result) {
  1989. if (empty($result->options['lock_name'])) {
  1990. return FALSE;
  1991. }
  1992. // Get the maximum amount of time this could take.
  1993. $times = array(
  1994. httprl_variable_get('httprl_timeout', HTTPRL_TIMEOUT),
  1995. httprl_variable_get('httprl_global_timeout', HTTPRL_GLOBAL_TIMEOUT),
  1996. );
  1997. if (isset($result->options['timeout'])) {
  1998. $times[] = $result->options['timeout'];
  1999. }
  2000. if (isset($result->options['global_timeout'])) {
  2001. $times[] = $result->options['global_timeout'];
  2002. }
  2003. // Acquire lock for this run.
  2004. $locked = FALSE;
  2005. $lock_counter = 0;
  2006. $name = $result->options['lock_name'];
  2007. while (!$locked && $lock_counter < 3) {
  2008. // Set lock to maximum amount of time.
  2009. $locked = lock_acquire($name, max($times));
  2010. $lock_counter++;
  2011. }
  2012. if (!$locked) {
  2013. return FALSE;
  2014. }
  2015. // Make sure lock exists after this process is dead.
  2016. // Remove from the global locks variable.
  2017. global $locks;
  2018. unset($locks[$name]);
  2019. // Remove the lock_id reference in the database.
  2020. if (httprl_variable_get('lock_inc', './includes/lock.inc') === './includes/lock.inc') {
  2021. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  2022. db_update('semaphore')
  2023. ->fields(array('value' => 'httprl'))
  2024. ->condition('name', $name)
  2025. ->condition('value', _lock_id())
  2026. ->execute();
  2027. }
  2028. else {
  2029. db_query("UPDATE {semaphore} SET value = '%s' WHERE name = '%s' AND value = '%s'", 'httprl', $name, _lock_id());
  2030. }
  2031. }
  2032. return TRUE;
  2033. }
  2034. /**
  2035. * Will decode chunked transfer-encoding and gzip/deflate content-encoding.
  2036. *
  2037. * @param object $result
  2038. * An object from httprl_send_request.
  2039. */
  2040. function httprl_decode_data(&$result) {
  2041. if (isset($result->headers['transfer-encoding']) && $result->headers['transfer-encoding'] == 'chunked') {
  2042. $stream_position = 0;
  2043. $output = '';
  2044. $data = $result->data;
  2045. while ($stream_position < httprl_strlen($data)) {
  2046. // Get the number of bytes to read for this chunk.
  2047. $rawnum = substr($data, $stream_position, strpos(substr($data, $stream_position), "\r\n") + 2);
  2048. $num = hexdec(trim($rawnum));
  2049. // Get the position to read from.
  2050. $stream_position += httprl_strlen($rawnum);
  2051. // Extract the chunk.
  2052. $chunk = substr($data, $stream_position, $num);
  2053. // Decompress if compressed.
  2054. if (isset($result->headers['content-encoding'])) {
  2055. if ($result->headers['content-encoding'] == 'gzip') {
  2056. $chunk = gzinflate(substr($chunk, 10));
  2057. }
  2058. elseif ($result->headers['content-encoding'] == 'deflate') {
  2059. $chunk = gzinflate($chunk);
  2060. }
  2061. }
  2062. // Glue the chunks together.
  2063. $output .= $chunk;
  2064. $stream_position += httprl_strlen($chunk);
  2065. }
  2066. $result->data = $output;
  2067. }
  2068. // Decompress if compressed.
  2069. elseif (isset($result->headers['content-encoding'])) {
  2070. if ($result->headers['content-encoding'] == 'gzip') {
  2071. $result->data = gzinflate(substr($result->data, 10));
  2072. }
  2073. elseif ($result->headers['content-encoding'] == 'deflate') {
  2074. $result->data = gzinflate($result->data);
  2075. }
  2076. }
  2077. // Cut up response for one sided Range requests.
  2078. if (array_key_exists('max_data_size', $result->options)) {
  2079. $result->code = 206;
  2080. // Make the data conform to the range request.
  2081. $new_data = array();
  2082. foreach ($result->ranges as $range) {
  2083. // Get only the last X number of bytes.
  2084. if (!is_numeric($range['start'])) {
  2085. $new_data[] = substr($result->data, -$range['end']);
  2086. }
  2087. // Get all but the first X number of bytes.
  2088. elseif (!is_numeric($range['end'])) {
  2089. $new_data[] = substr($result->data, $range['start']);
  2090. }
  2091. else {
  2092. $new_data[] = substr($result->data, $range['start'], ($range['end'] + 1) - $range['start']);
  2093. }
  2094. }
  2095. $result->data = implode('', $new_data);
  2096. // Fix content-length for fake 206s.
  2097. if (isset($result->headers['content-length'])) {
  2098. $result->headers['content-length'] = httprl_strlen($result->data);
  2099. }
  2100. }
  2101. // Reassemble multipart/byteranges response.
  2102. if (isset($result->headers['content-type']) && strpos($result->headers['content-type'], 'multipart/byteranges; boundary=') !== FALSE) {
  2103. // Get boundary string.
  2104. $boundary = "\r\n--" . substr($result->headers['content-type'], 31);
  2105. $datas = explode($boundary, $result->data);
  2106. $result->data = '';
  2107. foreach ($datas as $data) {
  2108. $split = preg_split("/\r\n\r\n|\n\n|\r\r/", $data, 2);
  2109. if (count($split) < 2) {
  2110. continue;
  2111. }
  2112. // Separate the data from the headers.
  2113. list($response, $data) = $split;
  2114. $response = array_filter(preg_split("/\r\n|\n|\r/", $response));
  2115. // Parse the response headers.
  2116. while ($line = trim(array_shift($response))) {
  2117. list($name, $value) = explode(':', $line, 2);
  2118. $name = strtolower($name);
  2119. // Add key value pairs to the header.
  2120. if ($name != 'content-range') {
  2121. $result->headers[$name] = trim($value);
  2122. }
  2123. }
  2124. $result->data .= $data;
  2125. }
  2126. // Fix content-length for multipart/byteranges.
  2127. if (isset($result->headers['content-length'])) {
  2128. $result->headers['content-length'] = httprl_strlen($result->data);
  2129. }
  2130. }
  2131. }
  2132. /**
  2133. * Reconstruct the internal redirect arrays.
  2134. *
  2135. * @param object $result
  2136. * An object from httprl_send_request.
  2137. */
  2138. function httprl_reconstruct_redirects(&$result) {
  2139. // Return if original_url is not set.
  2140. if (empty($result->options['internal_states']['original_url'])) {
  2141. return;
  2142. }
  2143. // Set the original url.
  2144. $result->url = $result->options['internal_states']['original_url'];
  2145. // Set the redirect code.
  2146. $result->redirect_code_array = $result->options['internal_states']['redirect_code_array'];
  2147. $result->redirect_code = array_pop($result->options['internal_states']['redirect_code_array']);
  2148. // Set the redirect url.
  2149. $result->redirect_url_array = $result->options['internal_states']['redirect_url_array'];
  2150. $result->redirect_url = array_pop($result->options['internal_states']['redirect_url_array']);
  2151. // Cleanup.
  2152. unset($result->options['internal_states']['original_url'], $result->options['internal_states']['redirect_code_array'], $result->options['internal_states']['redirect_url_array']);
  2153. if (empty($result->options['internal_states'])) {
  2154. unset($result->options['internal_states']);
  2155. }
  2156. }
  2157. /**
  2158. * Output text, close connection, continue processing in the background.
  2159. *
  2160. * @param string $output
  2161. * Text to output to open connection.
  2162. * @param bool $wait
  2163. * Wait 1 second?
  2164. * @param string $content_type
  2165. * Content type header.
  2166. * @param int $length
  2167. * Content length.
  2168. *
  2169. * @return bool
  2170. * Returns TRUE if operation worked, FALSE if it failed.
  2171. */
  2172. function httprl_background_processing($output, $wait = TRUE, $content_type = "text/html; charset=utf-8", $length = 0) {
  2173. // Can't do background processing if headers are already sent.
  2174. if (headers_sent()) {
  2175. return FALSE;
  2176. }
  2177. // Prime php for background operations.
  2178. // Remove any output buffers.
  2179. @ob_end_clean();
  2180. $loop = 0;
  2181. while (ob_get_level() && $loop < 25) {
  2182. @ob_end_clean();
  2183. $loop++;
  2184. }
  2185. // Ignore user aborts.
  2186. ignore_user_abort(TRUE);
  2187. // Output headers & data.
  2188. ob_start();
  2189. header("HTTP/1.0 200 OK");
  2190. header("Content-type: " . $content_type);
  2191. header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
  2192. header("Cache-Control: no-cache");
  2193. header("Cache-Control: must-revalidate");
  2194. header("Connection: close");
  2195. header('Etag: "' . microtime(TRUE) . '"');
  2196. print ($output);
  2197. $size = ob_get_length();
  2198. header("Content-Length: " . $size);
  2199. @ob_end_flush();
  2200. @ob_flush();
  2201. @flush();
  2202. if (function_exists('fastcgi_finish_request')) {
  2203. fastcgi_finish_request();
  2204. }
  2205. // Wait for 1 second.
  2206. if ($wait) {
  2207. sleep(1);
  2208. }
  2209. // Text returned and connection closed.
  2210. // Do background processing. Time taken after should not effect page load times.
  2211. return TRUE;
  2212. }
  2213. /**
  2214. * Get the length of a string in bytes.
  2215. *
  2216. * @param string $string
  2217. * get string length
  2218. */
  2219. function httprl_strlen($string) {
  2220. static $mb_strlen;
  2221. if (!isset($mb_strlen)) {
  2222. $mb_strlen = function_exists('mb_strlen');
  2223. }
  2224. if ($mb_strlen) {
  2225. return mb_strlen($string, '8bit');
  2226. }
  2227. else {
  2228. return strlen($string);
  2229. }
  2230. }
  2231. /**
  2232. * Alt to http_build_url().
  2233. *
  2234. * @see http://php.net/parse-url#85963
  2235. *
  2236. * @param array $parsed
  2237. * array from parse_url()
  2238. *
  2239. * @return string
  2240. * URI is returned.
  2241. */
  2242. function httprl_glue_url($parsed) {
  2243. if (!is_array($parsed)) {
  2244. return FALSE;
  2245. }
  2246. $uri = isset($parsed['scheme']) ? $parsed['scheme'] . ':' . ((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '';
  2247. $uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '';
  2248. $uri .= isset($parsed['host']) ? $parsed['host'] : '';
  2249. $uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : '';
  2250. if (isset($parsed['path'])) {
  2251. $uri .= (substr($parsed['path'], 0, 1) == '/') ? $parsed['path'] : ((!empty($uri) ? '/' : '') . $parsed['path']);
  2252. }
  2253. $uri .= isset($parsed['query']) ? '?' . $parsed['query'] : '';
  2254. $uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
  2255. return $uri;
  2256. }
  2257. /**
  2258. * Return the server schema (http or https).
  2259. *
  2260. * @return string
  2261. * http OR https.
  2262. */
  2263. function httprl_get_server_schema() {
  2264. return ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
  2265. || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
  2266. || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] == 'on')
  2267. ) ? 'https' : 'http';
  2268. }
  2269. /**
  2270. * Send out a fast 403 and exit.
  2271. */
  2272. function httprl_fast403($msg = '') {
  2273. global $base_path;
  2274. // Set headers.
  2275. if (!headers_sent()) {
  2276. header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
  2277. header('X-HTTPRL: Forbidden.');
  2278. }
  2279. // Print simple 403 page.
  2280. print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
  2281. print '<html>';
  2282. print '<head><title>403 Forbidden</title></head>';
  2283. print '<body><h1>Forbidden</h1>';
  2284. print '<p>You are not authorized to access this page.</p>';
  2285. print '<p><a href="' . $base_path . '">Home</a></p>';
  2286. print '<!-- httprl_fast403 ' . $msg . ' -->';
  2287. print '</body></html>';
  2288. // Exit Script.
  2289. httprl_call_exit();
  2290. }
  2291. /**
  2292. * Release a lock previously acquired by lock_acquire().
  2293. *
  2294. * This will release the named lock.
  2295. *
  2296. * @param string $name
  2297. * The name of the lock.
  2298. */
  2299. function httprl_lock_release($name) {
  2300. $lock_inc = httprl_variable_get('lock_inc', './includes/lock.inc');
  2301. // Core.
  2302. if ($lock_inc === './includes/lock.inc') {
  2303. global $locks;
  2304. unset($locks[$name]);
  2305. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  2306. db_delete('semaphore')
  2307. ->condition('name', $name)
  2308. ->execute();
  2309. }
  2310. else {
  2311. db_query("DELETE FROM {semaphore} WHERE name = '%s'", $name);
  2312. }
  2313. }
  2314. // Memcache storage module.
  2315. elseif (strpos($lock_inc, '/memcache_storage/includes/lock.inc') !== FALSE) {
  2316. global $locks;
  2317. // We unset unconditionally since caller assumes lock is released anyway.
  2318. unset($locks[$name]);
  2319. // Remove current lock from memcached pool.
  2320. if (MemcacheStorageAPI::get($name, 'semaphore')) {
  2321. MemcacheStorageAPI::delete($name, 'semaphore');
  2322. }
  2323. }
  2324. else {
  2325. lock_release($name);
  2326. }
  2327. }
  2328. /**
  2329. * If $data is bool or strlen = 0 use var_export. Recursively go deeper.
  2330. *
  2331. * @param mixed $data
  2332. * Data In.
  2333. * @param int $level
  2334. * (optional) At what level of the array/object are we at.
  2335. *
  2336. * @return mixed
  2337. * $data
  2338. */
  2339. function httprl_print_empty(&$data, $level = 0) {
  2340. $level++;
  2341. if ($level < 5) {
  2342. if (is_object($data)) {
  2343. $new_object = new stdClass();
  2344. $new_object->__original_class_name__ = get_class($data);
  2345. foreach ($data as $key => $values) {
  2346. $new_object->{$key} = httprl_print_empty($values, $level);
  2347. }
  2348. $data = $new_object;
  2349. }
  2350. elseif (is_array($data)) {
  2351. foreach ($data as &$values) {
  2352. $values = httprl_print_empty($values, $level);
  2353. }
  2354. }
  2355. elseif (is_bool($data) || strlen((string) $data) == 0) {
  2356. $data = strtoupper(var_export($data, TRUE));
  2357. }
  2358. elseif (is_string($data) && strlen($data) > HTTPRL_PR_MAX_STRING_LENGTH) {
  2359. // Do not try to print a string longer than 256KB.
  2360. // Some browsers have issues with very long documents.
  2361. $data = substr($data, 0, HTTPRL_PR_MAX_STRING_LENGTH);
  2362. }
  2363. }
  2364. return $data;
  2365. }
  2366. /**
  2367. * Pretty print data.
  2368. *
  2369. * @param string $input
  2370. * Data In.
  2371. *
  2372. * @return string
  2373. * Human readable HTML version of the data.
  2374. */
  2375. function httprl_pr($input) {
  2376. $old_setting = ini_set('mbstring.substitute_character', '"none"');
  2377. // Get extra arguments passed in.
  2378. $input = func_get_args();
  2379. // If bool or strlen = 0 use var_export on that variable.
  2380. $data = httprl_print_empty($input);
  2381. // Merge into base array if only one argument passed in.
  2382. if (count($data) == 1) {
  2383. $data = array_pop($data);
  2384. }
  2385. // Print_r the input.
  2386. $output = print_r($data, TRUE);
  2387. // Remove non UTF-8 Characters.
  2388. if (function_exists('mb_convert_encoding')) {
  2389. $translated = mb_convert_encoding($output, 'UTF-8', 'auto');
  2390. }
  2391. else {
  2392. $translated = @iconv('utf-8', 'utf-8//TRANSLIT//IGNORE', $output);
  2393. }
  2394. // Convert html entities.
  2395. $options = ENT_QUOTES;
  2396. if (defined('ENT_SUBSTITUTE')) {
  2397. $options = ENT_QUOTES | ENT_SUBSTITUTE;
  2398. }
  2399. elseif (defined('ENT_IGNORE')) {
  2400. $options = ENT_QUOTES | ENT_IGNORE;
  2401. }
  2402. $translated = htmlentities($translated, $options, 'UTF-8');
  2403. // Make sure the UTF-8 translation didn't kill the output.
  2404. $original_size = strlen($output);
  2405. $translated_size = strlen($translated);
  2406. $ratio = 0;
  2407. if ($original_size != 0) {
  2408. $ratio = ($original_size - $translated_size) / $original_size;
  2409. }
  2410. // Decide to use the original output or the translated one.
  2411. if (!empty($translated_size) && !empty($ratio) && $ratio < 0.5) {
  2412. $html_output = TRUE;
  2413. $output = $translated;
  2414. }
  2415. else {
  2416. $output = '<pre>' . str_replace(array('<', '>'), array('&lt;', '&gt;'), $output) . '</pre>';
  2417. }
  2418. // Remove extra new lines.
  2419. $output = array_filter(explode("\n", $output), 'strlen');
  2420. // Whitespace compression.
  2421. foreach ($output as $key => $value) {
  2422. if (str_replace(' ', '', $value) == "(") {
  2423. $output[$key - 1] .= ' (';
  2424. unset($output[$key]);
  2425. }
  2426. }
  2427. // Replace whitespace with html markup.
  2428. $output = implode("\n", $output);
  2429. if (!empty($html_output)) {
  2430. $output = str_replace(' ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br($output)) . '<br />';
  2431. }
  2432. ini_set('mbstring.substitute_character', $old_setting);
  2433. return $output;
  2434. }
  2435. /**
  2436. * Helper function for determining hosts excluded from needing a proxy.
  2437. *
  2438. * @return bool
  2439. * TRUE if a proxy should be used for this host.
  2440. */
  2441. function _httprl_use_proxy($host) {
  2442. $proxy_exceptions = httprl_variable_get('proxy_exceptions', array('localhost', '127.0.0.1'));
  2443. return !in_array(strtolower($host), $proxy_exceptions, TRUE);
  2444. }
  2445. /**
  2446. * Returns a persistent variable.
  2447. *
  2448. * This version will read directly from the database if value is not in global
  2449. * $conf variable.
  2450. *
  2451. * Case-sensitivity of the variable_* functions depends on the database
  2452. * collation used. To avoid problems, always use lower case for persistent
  2453. * variable names.
  2454. *
  2455. * @param string $name
  2456. * The name of the variable to return.
  2457. * @param mixed $default
  2458. * The default value to use if this variable has never been set.
  2459. * @return mixed
  2460. * The value of the variable.
  2461. *
  2462. * @see variable_del()
  2463. * @see variable_set()
  2464. */
  2465. function httprl_variable_get($name, $default = NULL) {
  2466. // Try global configuration variable first.
  2467. global $conf;
  2468. if (isset($conf[$name])) {
  2469. return $conf[$name];
  2470. }
  2471. // Try database next if not at a full bootstrap level.
  2472. if (function_exists('db_query') && !httprl_drupal_full_bootstrap()) {
  2473. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  2474. $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable} WHERE name = :name', array(':name' => $name))->fetchAllKeyed());
  2475. // Use the default if need be.
  2476. return isset($variables[$name]) ? $variables[$name] : $default;
  2477. }
  2478. else {
  2479. $result = db_query("SELECT value FROM {variable} WHERE name = '%s'", $name);
  2480. if (!empty($result)) {
  2481. $result = db_result($result);
  2482. if (!empty($result)) {
  2483. $value = @unserialize($result);
  2484. }
  2485. }
  2486. // Use the default if need be.
  2487. return isset($value) ? $value : $default;
  2488. }
  2489. }
  2490. else {
  2491. // Return default if database is not available or if at a full bootstrap.
  2492. return $default;
  2493. }
  2494. }
  2495. /**
  2496. * Run multiple functions or methods independently or chained.
  2497. *
  2498. * Example for running a Drupal 6 Database query.
  2499. * @code
  2500. * // Run 2 queries and get it's result.
  2501. * $max = db_result(db_query('SELECT MAX(wid) FROM {watchdog}'));
  2502. * $min = db_result(db_query('SELECT MIN(wid) FROM {watchdog}'));
  2503. * echo $max . ' ' . $min;
  2504. *
  2505. * // Doing the same thing as above but with a set of arrays.
  2506. * $max = '';
  2507. * $min = '';
  2508. * $args = array(
  2509. * array(
  2510. * 'type' => 'function',
  2511. * 'call' => 'db_query',
  2512. * 'args' => array('SELECT MAX(wid) FROM {watchdog}'),
  2513. * ),
  2514. * array(
  2515. * 'type' => 'function',
  2516. * 'call' => 'db_result',
  2517. * 'args' => array('last' => NULL),
  2518. * 'return' => &$max,
  2519. * ),
  2520. * array(
  2521. * 'type' => 'function',
  2522. * 'call' => 'db_query',
  2523. * 'args' => array('SELECT MIN(wid) FROM {watchdog}'),
  2524. * ),
  2525. * array(
  2526. * 'type' => 'function',
  2527. * 'call' => 'db_result',
  2528. * 'args' => array('last' => NULL),
  2529. * 'return' => &$min,
  2530. * ),
  2531. * );
  2532. * httprl_run_array($args);
  2533. * echo $max . ' ' . $min;
  2534. * @endcode
  2535. *
  2536. * Example for running a Drupal 7 Database query.
  2537. * @code
  2538. * // Run a query and get it's result.
  2539. * $min = db_select('watchdog', 'w')
  2540. * ->fields('w', array('wid'))
  2541. * ->orderBy('wid', 'DESC')
  2542. * ->range(999, 1)
  2543. * ->execute()
  2544. * ->fetchField();
  2545. * echo $min;
  2546. *
  2547. * // Doing the same thing as above but with a set of arrays.
  2548. * $min = '';
  2549. * $args = array(
  2550. * array(
  2551. * 'type' => 'function',
  2552. * 'call' => 'db_select',
  2553. * 'args' => array('watchdog', 'w',),
  2554. * ),
  2555. * array(
  2556. * 'type' => 'method',
  2557. * 'call' => 'fields',
  2558. * 'args' => array('w', array('wid')),
  2559. * ),
  2560. * array(
  2561. * 'type' => 'method',
  2562. * 'call' => 'orderBy',
  2563. * 'args' => array('wid', 'DESC'),
  2564. * ),
  2565. * array(
  2566. * 'type' => 'method',
  2567. * 'call' => 'range',
  2568. * 'args' => array(999, 1),
  2569. * ),
  2570. * array(
  2571. * 'type' => 'method',
  2572. * 'call' => 'execute',
  2573. * 'args' => array(),
  2574. * ),
  2575. * array(
  2576. * 'type' => 'method',
  2577. * 'call' => 'fetchField',
  2578. * 'args' => array(),
  2579. * 'return' => &$min,
  2580. * ),
  2581. * );
  2582. * httprl_run_array($args);
  2583. * echo $min;
  2584. * @endcode
  2585. *
  2586. * @param array $array
  2587. * 2 dimensional array
  2588. * array(
  2589. * array(
  2590. * 'type' => function or method
  2591. * 'call' => function name or name of object method
  2592. * 'args' => array(
  2593. * List of arguments to pass in. If you set the key to last, the return
  2594. * value of the last thing ran will be put in this place.
  2595. * 'last' => NULL
  2596. * ),
  2597. * 'return' => what was returned from this call.
  2598. * 'printed' => what was printed from this call.
  2599. * 'error' => any errors that might have occurred.
  2600. * 'last' => set the last variable to anything.
  2601. * )
  2602. * )
  2603. */
  2604. function httprl_run_array(&$array) {
  2605. $last = NULL;
  2606. foreach ($array as &$data) {
  2607. // Skip if no type is set.
  2608. if (!isset($data['type'])) {
  2609. continue;
  2610. }
  2611. // Set the last variable if so desired.
  2612. if (isset($data['last'])) {
  2613. $last = $data['last'];
  2614. }
  2615. // Replace the last key with the last thing that has been returned.
  2616. if (isset($data['args']) && array_key_exists('last', $data['args'])) {
  2617. $data['args']['last'] = $last;
  2618. $data['args'] = array_values($data['args']);
  2619. }
  2620. // Capture output if requested.
  2621. if (array_key_exists('printed', $data)) {
  2622. ob_start();
  2623. }
  2624. // Pass by reference trick for call_user_func_array().
  2625. $args = array();
  2626. if (isset($data['args']) && is_array($data['args'])) {
  2627. foreach ($data['args'] as &$arg) {
  2628. $args[] = &$arg;
  2629. }
  2630. }
  2631. // Start to capture errors.
  2632. $track_errors = ini_set('track_errors', '1');
  2633. $php_errormsg = '';
  2634. // Call a function or a method.
  2635. switch ($data['type']) {
  2636. case 'function':
  2637. if (function_exists($data['call'])) {
  2638. $last = call_user_func_array($data['call'], $args);
  2639. }
  2640. else {
  2641. $php_errormsg = 'Recoverable Fatal error: Call to undefined function ' . $data['call'] . '()';
  2642. }
  2643. break;
  2644. case 'method':
  2645. if (method_exists($last, $data['call'])) {
  2646. $last = call_user_func_array(array($last, $data['call']), $args);
  2647. }
  2648. else {
  2649. $php_errormsg = 'Recoverable Fatal error: Call to undefined method ' . get_class($last) . '::' . $data['call'] . '()';
  2650. }
  2651. break;
  2652. }
  2653. // Set any errors if any where thrown.
  2654. if (!empty($php_errormsg)) {
  2655. $data['error'] = $php_errormsg;
  2656. ini_set('track_errors', $track_errors);
  2657. watchdog('httprl', 'Error thrown in httprl_run_array(). <br /> @error', array('@error' => $php_errormsg), WATCHDOG_ERROR);
  2658. }
  2659. // End capture.
  2660. if (array_key_exists('printed', $data)) {
  2661. $data['printed'] = ob_get_contents();
  2662. ob_end_clean();
  2663. }
  2664. // Set what was returned from each call.
  2665. if (array_key_exists('return', $data)) {
  2666. $data['return'] = $last;
  2667. }
  2668. }
  2669. return array('args' => array($array));
  2670. }
  2671. /**
  2672. * Run a single function.
  2673. *
  2674. * @param string $function
  2675. * Name of function to run.
  2676. * @param array $input_args
  2677. * list of arguments to pass along to the function.
  2678. */
  2679. function httprl_run_function($function, &$input_args) {
  2680. // Pass by reference trick for call_user_func_array().
  2681. $args = array();
  2682. foreach ($input_args as &$arg) {
  2683. $args[] = &$arg;
  2684. }
  2685. // Capture anything printed out.
  2686. ob_start();
  2687. // Start to capture errors.
  2688. $track_errors = ini_set('track_errors', '1');
  2689. $php_errormsg = '';
  2690. // Run function.
  2691. $return = NULL;
  2692. // Do not let an exception cause issues.
  2693. try {
  2694. if (function_exists($function)) {
  2695. $return = call_user_func_array($function, $args);
  2696. }
  2697. else {
  2698. $php_errormsg = 'Recoverable Fatal error: Call to undefined function ' . $function . '()';
  2699. }
  2700. }
  2701. catch (Exception $e) {
  2702. $php_errormsg = $e;
  2703. }
  2704. $printed = ob_get_contents();
  2705. ob_end_clean();
  2706. // Create data array.
  2707. $data = array('return' => $return, 'args' => $args, 'printed' => $printed);
  2708. // Set any errors if any where thrown.
  2709. if (!empty($php_errormsg)) {
  2710. $data['error'] = $php_errormsg;
  2711. ini_set('track_errors', $track_errors);
  2712. watchdog('httprl', 'Error thrown in httprl_run_function(). <br /> @error', array('@error' => $php_errormsg), WATCHDOG_ERROR);
  2713. }
  2714. return $data;
  2715. }
  2716. /**
  2717. * Implements hook_boot().
  2718. */
  2719. function httprl_boot() {
  2720. global $base_root;
  2721. $full_url = $base_root . request_uri();
  2722. // Return if this is not a httprl_async_function_callback request.
  2723. if ( strpos($full_url, '/httprl_async_function_callback') === FALSE
  2724. || $_SERVER['REQUEST_METHOD'] !== 'POST'
  2725. || empty($_POST['master_key'])
  2726. || empty($_POST['temp_key'])
  2727. || strpos($_POST['temp_key'], 'httprl_') !== 0
  2728. || !empty($_POST['function'])
  2729. ) {
  2730. return NULL;
  2731. }
  2732. // Load httprl.async.inc.
  2733. if (defined('DRUPAL_ROOT')) {
  2734. require_once DRUPAL_ROOT . '/' . dirname(drupal_get_filename('module', 'httprl')) . '/httprl.async.inc';
  2735. }
  2736. else {
  2737. require_once './' . dirname(drupal_get_filename('module', 'httprl')) . '/httprl.async.inc';
  2738. }
  2739. httprl_async_page();
  2740. }
  2741. /**
  2742. * Gets the private key variable.
  2743. *
  2744. * @return string
  2745. * The private key.
  2746. */
  2747. function httprl_drupal_get_private_key() {
  2748. $full_bootstrap = httprl_drupal_full_bootstrap();
  2749. $private_key = $full_bootstrap ? drupal_get_private_key() : httprl_variable_get('drupal_private_key', 0);
  2750. return $private_key;
  2751. }
  2752. /**
  2753. * Performs end-of-request tasks and/or call exit directly.
  2754. */
  2755. function httprl_call_exit() {
  2756. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7 && drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
  2757. drupal_exit();
  2758. }
  2759. else {
  2760. exit;
  2761. }
  2762. }
  2763. /**
  2764. * Sees if Drupal has been fully booted.
  2765. *
  2766. * @return Bool
  2767. * TRUE if DRUPAL_BOOTSTRAP_FULL.
  2768. * FALSE if not DRUPAL_BOOTSTRAP_FULL.
  2769. */
  2770. function httprl_drupal_full_bootstrap() {
  2771. static $full_bootstrap;
  2772. if (!isset($full_bootstrap)) {
  2773. // See if a full bootstrap has been done given the Drupal version.
  2774. if (defined('VERSION') && substr(VERSION, 0, 1) >= 7) {
  2775. $level = drupal_bootstrap();
  2776. $full_bootstrap = ($level == DRUPAL_BOOTSTRAP_FULL) ? TRUE : FALSE;
  2777. }
  2778. else {
  2779. $full_bootstrap = isset($GLOBALS['multibyte']) ? TRUE : FALSE;
  2780. }
  2781. }
  2782. return $full_bootstrap;
  2783. }
  2784. /**
  2785. * Sees if httprl can run a background callback.
  2786. *
  2787. * @return Bool
  2788. * TRUE or FALSE.
  2789. */
  2790. function httprl_is_background_callback_capable() {
  2791. // Check if site is offline.
  2792. if ((defined('VERSION') && substr(VERSION, 0, 1) >= 7 && httprl_variable_get('maintenance_mode', 0)) || httprl_variable_get('site_offline', 0)) {
  2793. return FALSE;
  2794. }
  2795. // Check that the httprl_background_callback variable is enabled.
  2796. if (!httprl_variable_get('httprl_background_callback', HTTPRL_BACKGROUND_CALLBACK)) {
  2797. return FALSE;
  2798. }
  2799. // Check that the callback in menu works.
  2800. if ( httprl_drupal_full_bootstrap()
  2801. && function_exists('menu_get_item')
  2802. && !menu_get_item('httprl_async_function_callback')
  2803. ) {
  2804. return FALSE;
  2805. }
  2806. // All checks passed.
  2807. return TRUE;
  2808. }
  2809. /**
  2810. * Sets the global user to the given user ID.
  2811. *
  2812. * @param int $uid
  2813. * Integer specifying the user ID to load.
  2814. */
  2815. function httprl_set_user($uid) {
  2816. global $user;
  2817. $account = user_load($uid);
  2818. if (!empty($account)) {
  2819. $user = $account;
  2820. return TRUE;
  2821. }
  2822. }
  2823. /**
  2824. * Sets the global $_GET['q'] parameter.
  2825. *
  2826. * @param string $q
  2827. * Internal URL.
  2828. */
  2829. function httprl_set_q($q) {
  2830. $_GET['q'] = $q;
  2831. }
  2832. /**
  2833. * Queue Callback to run In a New Process.
  2834. *
  2835. * @see call_user_func_array()
  2836. *
  2837. * @param callback
  2838. * The callable to be called.
  2839. * @param param_arr
  2840. * The parameters to be passed to the callback, as an indexed array.
  2841. * @param $return
  2842. * Set to TRUE if you want something returned.
  2843. * @param $httprl_options
  2844. * Options to pass along to httprl_queue_background_callback.
  2845. * @return
  2846. * Reference to the return varible OR NULL if $return is FALSE.
  2847. */
  2848. function &httprl_qcinp($callback, $param_arr = array(), $return = TRUE, $httprl_options = array()) {
  2849. $return_var = NULL;
  2850. // Setup callback options array.
  2851. $callback_options[0]['function'] = $callback;
  2852. if ($return) {
  2853. $return_var = '';
  2854. $callback_options[0]['return'] = &$return_var;
  2855. }
  2856. if (isset($httprl_options['context'])) {
  2857. $callback_options[0]['context'] = $httprl_options['context'];
  2858. unset($httprl_options['context']);
  2859. }
  2860. $callback_options[0]['options'] = $httprl_options;
  2861. // Include function arguments.
  2862. $callback_options = array_merge($callback_options, $param_arr);
  2863. // Queue up the request.
  2864. httprl_queue_background_callback($callback_options);
  2865. return $return_var;
  2866. }
  2867. /**
  2868. * Given an array of data, use mutiple processes to crunch it.
  2869. *
  2870. * Similar to MapReduce.
  2871. *
  2872. * @see http://php.net/array-chunk#63394
  2873. *
  2874. * @param $callback
  2875. * The function to run
  2876. * @param $data
  2877. * The data to process.
  2878. * @return
  2879. * Array of returned results.
  2880. */
  2881. function httprl_batch_callback($callback, $data, $options = array()) {
  2882. // Set defaults.
  2883. $results = array();
  2884. $unified_result = NULL;
  2885. $number_of_items = count($data);
  2886. $options += array(
  2887. 'max_batchsize' => 30,
  2888. 'threads' => 3,
  2889. 'timeout' => 120,
  2890. 'multiple_helper' => FALSE,
  2891. );
  2892. // Shrink batchsize to evenly distribute workload if needed.
  2893. if ($number_of_items < $options['max_batchsize']*$options['threads']) {
  2894. $options['max_batchsize'] = ceil($number_of_items/$options['threads']);
  2895. }
  2896. // Chunk the data.
  2897. $data = array_chunk($data, $options['max_batchsize'], TRUE);
  2898. // Convert options to httprl_queue_background_callback options.
  2899. unset($options['max_batchsize']);
  2900. $options['domain_connections'] = $options['threads'];
  2901. unset($options['threads']);
  2902. $multiple = $options['multiple_helper'];
  2903. unset($options['multiple_helper']);
  2904. // Queue up the processes.
  2905. if ($multiple) {
  2906. foreach ($data as $key => $values) {
  2907. $results[$key] = &httprl_qcinp('httprl_run_multiple', array($callback, $values), TRUE, $options);
  2908. }
  2909. }
  2910. else {
  2911. foreach ($data as $key => $values) {
  2912. $results[$key] = &httprl_qcinp($callback, array($values), TRUE, $options);
  2913. }
  2914. }
  2915. // Execute in parallel.
  2916. httprl_send_request();
  2917. // Try to merge the results into one.
  2918. $unified = TRUE;
  2919. $is_assoc = TRUE;
  2920. foreach ($results as $key => $value) {
  2921. if (is_null($unified_result)) {
  2922. $unified_result = $results[$key];
  2923. }
  2924. elseif (is_string($results[$key]) && is_string($unified_result)) {
  2925. $unified_result .= $results[$key];
  2926. }
  2927. elseif (is_array($results[$key]) && is_array($unified_result)) {
  2928. if ($is_assoc && httprl_is_array_assoc($results[$key]) && httprl_is_array_assoc($unified_result)) {
  2929. $unified_result = httprl_lossless_assoc_array_merge($unified_result, $results[$key]);
  2930. }
  2931. else {
  2932. $is_assoc = FALSE;
  2933. $unified_result += $results[$key];
  2934. }
  2935. }
  2936. else {
  2937. $unified = FALSE;
  2938. break;
  2939. }
  2940. }
  2941. // Return results.
  2942. if ($unified) {
  2943. return $unified_result;
  2944. }
  2945. else {
  2946. return $results;
  2947. }
  2948. }
  2949. /**
  2950. * Given an array return TRUE if all keys are numeric.
  2951. *
  2952. * @see http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential/2444661#2444661
  2953. *
  2954. * @param $array
  2955. * The data to process.
  2956. * @return
  2957. * TRUE or FALSE.
  2958. */
  2959. function httprl_is_array_assoc($array) {
  2960. return ctype_digit(implode('', array_keys($array)));
  2961. }
  2962. /**
  2963. * Merge mutiple associative arrays into one.
  2964. *
  2965. * @see http://stackoverflow.com/questions/2148694/how-to-combine-2-associative-arrays-in-php-such-that-we-do-not-overwrite-any-dup/2152054#2152054
  2966. *
  2967. * @param ...
  2968. * Arrays to merge.
  2969. * @return
  2970. * Merged array.
  2971. */
  2972. function httprl_lossless_assoc_array_merge() {
  2973. $arrays = func_get_args();
  2974. $data = array();
  2975. foreach ($arrays as $a) {
  2976. foreach ($a as $k => $v) {
  2977. if (isset($data[$k])) {
  2978. $data[] = $v;
  2979. }
  2980. else {
  2981. $data[$k] = $v;
  2982. }
  2983. }
  2984. }
  2985. return $data;
  2986. }
  2987. /**
  2988. * Run array of data through callback.
  2989. *
  2990. *
  2991. * @param $data
  2992. * The data to process.
  2993. * @param $callback
  2994. * The function to run
  2995. * @return
  2996. * Array of results.
  2997. */
  2998. function httprl_run_multiple($callback, $data) {
  2999. $results = array();
  3000. foreach ($data as $key => $values) {
  3001. $results[$key] = call_user_func_array($callback, array($values));
  3002. }
  3003. return $results;
  3004. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.