Requests_Transport_cURL

CURL HTTP transport.

Defined (1)

The class is defined in the following location(s).

/wp-includes/Requests/Transport/cURL.php  
  1. class Requests_Transport_cURL implements Requests_Transport { 
  2. const CURL_7_10_5 = 0x070A05; 
  3. const CURL_7_16_2 = 0x071002; 
  4.  
  5. /** 
  6. * Raw HTTP data 
  7. * @var string 
  8. */ 
  9. public $headers = ''; 
  10.  
  11. /** 
  12. * Raw body data 
  13. * @var string 
  14. */ 
  15. public $response_data = ''; 
  16.  
  17. /** 
  18. * Information on the current request 
  19. * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo} 
  20. */ 
  21. public $info; 
  22.  
  23. /** 
  24. * Version string 
  25. * @var long 
  26. */ 
  27. public $version; 
  28.  
  29. /** 
  30. * cURL handle 
  31. * @var resource 
  32. */ 
  33. protected $handle; 
  34.  
  35. /** 
  36. * Hook dispatcher instance 
  37. * @var Requests_Hooks 
  38. */ 
  39. protected $hooks; 
  40.  
  41. /** 
  42. * Have we finished the headers yet? 
  43. * @var boolean 
  44. */ 
  45. protected $done_headers = false; 
  46.  
  47. /** 
  48. * If streaming to a file, keep the file pointer 
  49. * @var resource 
  50. */ 
  51. protected $stream_handle; 
  52.  
  53. /** 
  54. * How many bytes are in the response body? 
  55. * @var int 
  56. */ 
  57. protected $response_bytes; 
  58.  
  59. /** 
  60. * What's the maximum number of bytes we should keep? 
  61. * @var int|bool Byte count, or false if no limit. 
  62. */ 
  63. protected $response_byte_limit; 
  64.  
  65. /** 
  66. * Constructor 
  67. */ 
  68. public function __construct() { 
  69. $curl = curl_version(); 
  70. $this->version = $curl['version_number']; 
  71. $this->handle = curl_init(); 
  72.  
  73. curl_setopt($this->handle, CURLOPT_HEADER, false); 
  74. curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); 
  75. if ($this->version >= self::CURL_7_10_5) { 
  76. curl_setopt($this->handle, CURLOPT_ENCODING, ''); 
  77. if (defined('CURLOPT_PROTOCOLS')) { 
  78. curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); 
  79. if (defined('CURLOPT_REDIR_PROTOCOLS')) { 
  80. curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); 
  81.  
  82. /** 
  83. * Destructor 
  84. */ 
  85. public function __destruct() { 
  86. if (is_resource($this->handle)) { 
  87. curl_close($this->handle); 
  88.  
  89. /** 
  90. * Perform a request 
  91. * @throws Requests_Exception On a cURL error (`curlerror`) 
  92. * @param string $url URL to request 
  93. * @param array $headers Associative array of request headers 
  94. * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 
  95. * @param array $options Request options, see {@see Requests::response()} for documentation 
  96. * @return string Raw HTTP result 
  97. */ 
  98. public function request($url, $headers = array(), $data = array(), $options = array()) { 
  99. $this->hooks = $options['hooks']; 
  100.  
  101. $this->setup_handle($url, $headers, $data, $options); 
  102.  
  103. $options['hooks']->dispatch('curl.before_send', array(&$this->handle)); 
  104.  
  105. if ($options['filename'] !== false) { 
  106. $this->stream_handle = fopen($options['filename'], 'wb'); 
  107.  
  108. $this->response_data = ''; 
  109. $this->response_bytes = 0; 
  110. $this->response_byte_limit = false; 
  111. if ($options['max_bytes'] !== false) { 
  112. $this->response_byte_limit = $options['max_bytes']; 
  113.  
  114. if (isset($options['verify'])) { 
  115. if ($options['verify'] === false) { 
  116. curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); 
  117. curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0); 
  118. elseif (is_string($options['verify'])) { 
  119. curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']); 
  120.  
  121. if (isset($options['verifyname']) && $options['verifyname'] === false) { 
  122. curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); 
  123.  
  124. curl_exec($this->handle); 
  125. $response = $this->response_data; 
  126.  
  127. $options['hooks']->dispatch('curl.after_send', array()); 
  128.  
  129. if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) { 
  130. // Reset encoding and try again 
  131. curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); 
  132.  
  133. $this->response_data = ''; 
  134. $this->response_bytes = 0; 
  135. curl_exec($this->handle); 
  136. $response = $this->response_data; 
  137.  
  138. $this->process_response($response, $options); 
  139.  
  140. // Need to remove the $this reference from the curl handle. 
  141. // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called. 
  142. curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); 
  143. curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); 
  144.  
  145. return $this->headers; 
  146.  
  147. /** 
  148. * Send multiple requests simultaneously 
  149. * @param array $requests Request data 
  150. * @param array $options Global options 
  151. * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well) 
  152. */ 
  153. public function request_multiple($requests, $options) { 
  154. // If you're not requesting, we can't get any responses \_(*)_/ 
  155. if (empty($requests)) { 
  156. return array(); 
  157.  
  158. $multihandle = curl_multi_init(); 
  159. $subrequests = array(); 
  160. $subhandles = array(); 
  161.  
  162. $class = get_class($this); 
  163. foreach ($requests as $id => $request) { 
  164. $subrequests[$id] = new $class(); 
  165. $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); 
  166. $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id])); 
  167. curl_multi_add_handle($multihandle, $subhandles[$id]); 
  168.  
  169. $completed = 0; 
  170. $responses = array(); 
  171.  
  172. $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle)); 
  173.  
  174. do { 
  175. $active = false; 
  176.  
  177. do { 
  178. $status = curl_multi_exec($multihandle, $active); 
  179. while ($status === CURLM_CALL_MULTI_PERFORM); 
  180.  
  181. $to_process = array(); 
  182.  
  183. // Read the information as needed 
  184. while ($done = curl_multi_info_read($multihandle)) { 
  185. $key = array_search($done['handle'], $subhandles, true); 
  186. if (!isset($to_process[$key])) { 
  187. $to_process[$key] = $done; 
  188.  
  189. // Parse the finished requests before we start getting the new ones 
  190. foreach ($to_process as $key => $done) { 
  191. $options = $requests[$key]['options']; 
  192. if (CURLE_OK !== $done['result']) { 
  193. //get error string for handle. 
  194. $reason = curl_error($done['handle']); 
  195. $exception = new Requests_Exception_Transport_cURL( 
  196. $reason,  
  197. Requests_Exception_Transport_cURL::EASY,  
  198. $done['handle'],  
  199. $done['result'] 
  200. ); 
  201. $responses[$key] = $exception; 
  202. $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key])); 
  203. else { 
  204. $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); 
  205.  
  206. $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key])); 
  207.  
  208. curl_multi_remove_handle($multihandle, $done['handle']); 
  209. curl_close($done['handle']); 
  210.  
  211. if (!is_string($responses[$key])) { 
  212. $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key)); 
  213. $completed++; 
  214. while ($active || $completed < count($subrequests)); 
  215.  
  216. $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle)); 
  217.  
  218. curl_multi_close($multihandle); 
  219.  
  220. return $responses; 
  221.  
  222. /** 
  223. * Get the cURL handle for use in a multi-request 
  224. * @param string $url URL to request 
  225. * @param array $headers Associative array of request headers 
  226. * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 
  227. * @param array $options Request options, see {@see Requests::response()} for documentation 
  228. * @return resource Subrequest's cURL handle 
  229. */ 
  230. public function &get_subrequest_handle($url, $headers, $data, $options) { 
  231. $this->setup_handle($url, $headers, $data, $options); 
  232.  
  233. if ($options['filename'] !== false) { 
  234. $this->stream_handle = fopen($options['filename'], 'wb'); 
  235.  
  236. $this->response_data = ''; 
  237. $this->response_bytes = 0; 
  238. $this->response_byte_limit = false; 
  239. if ($options['max_bytes'] !== false) { 
  240. $this->response_byte_limit = $options['max_bytes']; 
  241. $this->hooks = $options['hooks']; 
  242.  
  243. return $this->handle; 
  244.  
  245. /** 
  246. * Setup the cURL handle for the given data 
  247. * @param string $url URL to request 
  248. * @param array $headers Associative array of request headers 
  249. * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD 
  250. * @param array $options Request options, see {@see Requests::response()} for documentation 
  251. */ 
  252. protected function setup_handle($url, $headers, $data, $options) { 
  253. $options['hooks']->dispatch('curl.before_request', array(&$this->handle)); 
  254.  
  255. // Force closing the connection for old versions of cURL (<7.22). 
  256. if ( ! isset( $headers['Connection'] ) ) { 
  257. $headers['Connection'] = 'close'; 
  258.  
  259. $headers = Requests::flatten($headers); 
  260.  
  261. if (!empty($data)) { 
  262. $data_format = $options['data_format']; 
  263.  
  264. if ($data_format === 'query') { 
  265. $url = self::format_get($url, $data); 
  266. $data = ''; 
  267. elseif (!is_string($data)) { 
  268. $data = http_build_query($data, null, '&'); 
  269.  
  270. switch ($options['type']) { 
  271. case Requests::POST: 
  272. curl_setopt($this->handle, CURLOPT_POST, true); 
  273. curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); 
  274. break; 
  275. case Requests::HEAD: 
  276. curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); 
  277. curl_setopt($this->handle, CURLOPT_NOBODY, true); 
  278. break; 
  279. case Requests::TRACE: 
  280. curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); 
  281. break; 
  282. case Requests::PATCH: 
  283. case Requests::PUT: 
  284. case Requests::DELETE: 
  285. case Requests::OPTIONS: 
  286. default: 
  287. curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); 
  288. if (!empty($data)) { 
  289. curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); 
  290.  
  291. // cURL requires a minimum timeout of 1 second when using the system 
  292. // DNS resolver, as it uses `alarm()`, which is second resolution only. 
  293. // There's no way to detect which DNS resolver is being used from our 
  294. // end, so we need to round up regardless of the supplied timeout. 
  295. // 
  296. // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 
  297. $timeout = max($options['timeout'], 1); 
  298.  
  299. if (is_int($timeout) || $this->version < self::CURL_7_16_2) { 
  300. curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); 
  301. else { 
  302. curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); 
  303.  
  304. if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { 
  305. curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout'])); 
  306. else { 
  307. curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); 
  308. curl_setopt($this->handle, CURLOPT_URL, $url); 
  309. curl_setopt($this->handle, CURLOPT_REFERER, $url); 
  310. curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); 
  311. curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); 
  312.  
  313. if ($options['protocol_version'] === 1.1) { 
  314. curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 
  315. else { 
  316. curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 
  317.  
  318. if (true === $options['blocking']) { 
  319. curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers')); 
  320. curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body')); 
  321. curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); 
  322.  
  323. /** 
  324. * Process a response 
  325. * @param string $response Response data from the body 
  326. * @param array $options Request options 
  327. * @return string HTTP response data including headers 
  328. */ 
  329. public function process_response($response, $options) { 
  330. if ($options['blocking'] === false) { 
  331. $fake_headers = ''; 
  332. $options['hooks']->dispatch('curl.after_request', array(&$fake_headers)); 
  333. return false; 
  334. if ($options['filename'] !== false) { 
  335. fclose($this->stream_handle); 
  336. $this->headers = trim($this->headers); 
  337. else { 
  338. $this->headers .= $response; 
  339.  
  340. if (curl_errno($this->handle)) { 
  341. $error = sprintf( 
  342. 'cURL error %s: %s',  
  343. curl_errno($this->handle),  
  344. curl_error($this->handle) 
  345. ); 
  346. throw new Requests_Exception($error, 'curlerror', $this->handle); 
  347. $this->info = curl_getinfo($this->handle); 
  348.  
  349. $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info)); 
  350. return $this->headers; 
  351.  
  352. /** 
  353. * Collect the headers as they are received 
  354. * @param resource $handle cURL resource 
  355. * @param string $headers Header string 
  356. * @return integer Length of provided header 
  357. */ 
  358. public function stream_headers($handle, $headers) { 
  359. // Why do we do this? cURL will send both the final response and any 
  360. // interim responses, such as a 100 Continue. We don't need that. 
  361. // (We may want to keep this somewhere just in case) 
  362. if ($this->done_headers) { 
  363. $this->headers = ''; 
  364. $this->done_headers = false; 
  365. $this->headers .= $headers; 
  366.  
  367. if ($headers === "\r\n") { 
  368. $this->done_headers = true; 
  369. return strlen($headers); 
  370.  
  371. /** 
  372. * Collect data as it's received 
  373. * @since 1.6.1 
  374. * @param resource $handle cURL resource 
  375. * @param string $data Body data 
  376. * @return integer Length of provided data 
  377. */ 
  378. protected function stream_body($handle, $data) { 
  379. $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit)); 
  380. $data_length = strlen($data); 
  381.  
  382. // Are we limiting the response size? 
  383. if ($this->response_byte_limit) { 
  384. if ($this->response_bytes === $this->response_byte_limit) { 
  385. // Already at maximum, move on 
  386. return $data_length; 
  387.  
  388. if (($this->response_bytes + $data_length) > $this->response_byte_limit) { 
  389. // Limit the length 
  390. $limited_length = ($this->response_byte_limit - $this->response_bytes); 
  391. $data = substr($data, 0, $limited_length); 
  392.  
  393. if ($this->stream_handle) { 
  394. fwrite($this->stream_handle, $data); 
  395. else { 
  396. $this->response_data .= $data; 
  397.  
  398. $this->response_bytes += strlen($data); 
  399. return $data_length; 
  400.  
  401. /** 
  402. * Format a URL given GET data 
  403. * @param string $url 
  404. * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query} 
  405. * @return string URL with data 
  406. */ 
  407. protected static function format_get($url, $data) { 
  408. if (!empty($data)) { 
  409. $url_parts = parse_url($url); 
  410. if (empty($url_parts['query'])) { 
  411. $query = $url_parts['query'] = ''; 
  412. else { 
  413. $query = $url_parts['query']; 
  414.  
  415. $query .= '&' . http_build_query($data, null, '&'); 
  416. $query = trim($query, '&'); 
  417.  
  418. if (empty($url_parts['query'])) { 
  419. $url .= '?' . $query; 
  420. else { 
  421. $url = str_replace($url_parts['query'], $query, $url); 
  422. return $url; 
  423.  
  424. /** 
  425. * Whether this transport is valid 
  426. * @codeCoverageIgnore 
  427. * @return boolean True if the transport is valid, false otherwise. 
  428. */ 
  429. public static function test($capabilities = array()) { 
  430. if (!function_exists('curl_init') || !function_exists('curl_exec')) { 
  431. return false; 
  432.  
  433. // If needed, check that our installed curl version supports SSL 
  434. if (isset($capabilities['ssl']) && $capabilities['ssl']) { 
  435. $curl_version = curl_version(); 
  436. if (!(CURL_VERSION_SSL & $curl_version['features'])) { 
  437. return false; 
  438.  
  439. return true;