WP_Http_Curl

Core class used to integrate Curl as an HTTP transport.

Defined (1)

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

/wp-includes/class-wp-http-curl.php  
  1. class WP_Http_Curl { 
  2.  
  3. /** 
  4. * Temporary header storage for during requests. 
  5. * @since 3.2.0 
  6. * @access private 
  7. * @var string 
  8. */ 
  9. private $headers = ''; 
  10.  
  11. /** 
  12. * Temporary body storage for during requests. 
  13. * @since 3.6.0 
  14. * @access private 
  15. * @var string 
  16. */ 
  17. private $body = ''; 
  18.  
  19. /** 
  20. * The maximum amount of data to receive from the remote server. 
  21. * @since 3.6.0 
  22. * @access private 
  23. * @var int 
  24. */ 
  25. private $max_body_length = false; 
  26.  
  27. /** 
  28. * The file resource used for streaming to file. 
  29. * @since 3.6.0 
  30. * @access private 
  31. * @var resource 
  32. */ 
  33. private $stream_handle = false; 
  34.  
  35. /** 
  36. * The total bytes written in the current request. 
  37. * @since 4.1.0 
  38. * @access private 
  39. * @var int 
  40. */ 
  41. private $bytes_written_total = 0; 
  42.  
  43. /** 
  44. * Send a HTTP request to a URI using cURL extension. 
  45. * @access public 
  46. * @since 2.7.0 
  47. * @param string $url The request URL. 
  48. * @param string|array $args Optional. Override the defaults. 
  49. * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error 
  50. */ 
  51. public function request($url, $args = array()) { 
  52. $defaults = array( 
  53. 'method' => 'GET', 'timeout' => 5,  
  54. 'redirection' => 5, 'httpversion' => '1.0',  
  55. 'blocking' => true,  
  56. 'headers' => array(), 'body' => null, 'cookies' => array() 
  57. ); 
  58.  
  59. $r = wp_parse_args( $args, $defaults ); 
  60.  
  61. if ( isset( $r['headers']['User-Agent'] ) ) { 
  62. $r['user-agent'] = $r['headers']['User-Agent']; 
  63. unset( $r['headers']['User-Agent'] ); 
  64. } elseif ( isset( $r['headers']['user-agent'] ) ) { 
  65. $r['user-agent'] = $r['headers']['user-agent']; 
  66. unset( $r['headers']['user-agent'] ); 
  67.  
  68. // Construct Cookie: header if any cookies are set. 
  69. WP_Http::buildCookieHeader( $r ); 
  70.  
  71. $handle = curl_init(); 
  72.  
  73. // cURL offers really easy proxy support. 
  74. $proxy = new WP_HTTP_Proxy(); 
  75.  
  76. if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) { 
  77.  
  78. curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP ); 
  79. curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() ); 
  80. curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() ); 
  81.  
  82. if ( $proxy->use_authentication() ) { 
  83. curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY ); 
  84. curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() ); 
  85.  
  86. $is_local = isset($r['local']) && $r['local']; 
  87. $ssl_verify = isset($r['sslverify']) && $r['sslverify']; 
  88. if ( $is_local ) { 
  89. /** This filter is documented in wp-includes/class-wp-http-streams.php */ 
  90. $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify ); 
  91. } elseif ( ! $is_local ) { 
  92. /** This filter is documented in wp-includes/class-wp-http-streams.php */ 
  93. $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify ); 
  94.  
  95. /** 
  96. * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since. 
  97. * a value of 0 will allow an unlimited timeout. 
  98. */ 
  99. $timeout = (int) ceil( $r['timeout'] ); 
  100. curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout ); 
  101. curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout ); 
  102.  
  103. curl_setopt( $handle, CURLOPT_URL, $url); 
  104. curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true ); 
  105. curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false ); 
  106. curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify ); 
  107.  
  108. if ( $ssl_verify ) { 
  109. curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] ); 
  110.  
  111. curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] ); 
  112.  
  113. /** 
  114. * The option doesn't work with safe mode or when open_basedir is set, and there's 
  115. * a bug #17490 with redirected POST requests, so handle redirections outside Curl. 
  116. */ 
  117. curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false ); 
  118. if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4 
  119. curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS ); 
  120.  
  121. switch ( $r['method'] ) { 
  122. case 'HEAD': 
  123. curl_setopt( $handle, CURLOPT_NOBODY, true ); 
  124. break; 
  125. case 'POST': 
  126. curl_setopt( $handle, CURLOPT_POST, true ); 
  127. curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); 
  128. break; 
  129. case 'PUT': 
  130. curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' ); 
  131. curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); 
  132. break; 
  133. default: 
  134. curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] ); 
  135. if ( ! is_null( $r['body'] ) ) 
  136. curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] ); 
  137. break; 
  138.  
  139. if ( true === $r['blocking'] ) { 
  140. curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); 
  141. curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) ); 
  142.  
  143. curl_setopt( $handle, CURLOPT_HEADER, false ); 
  144.  
  145. if ( isset( $r['limit_response_size'] ) ) 
  146. $this->max_body_length = intval( $r['limit_response_size'] ); 
  147. else 
  148. $this->max_body_length = false; 
  149.  
  150. // If streaming to a file open a file handle, and setup our curl streaming handler. 
  151. if ( $r['stream'] ) { 
  152. if ( ! WP_DEBUG ) 
  153. $this->stream_handle = @fopen( $r['filename'], 'w+' ); 
  154. else 
  155. $this->stream_handle = fopen( $r['filename'], 'w+' ); 
  156. if ( ! $this->stream_handle ) 
  157. return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); 
  158. } else { 
  159. $this->stream_handle = false; 
  160.  
  161. if ( !empty( $r['headers'] ) ) { 
  162. // cURL expects full header strings in each element. 
  163. $headers = array(); 
  164. foreach ( $r['headers'] as $name => $value ) { 
  165. $headers[] = "{$name}: $value"; 
  166. curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); 
  167.  
  168. if ( $r['httpversion'] == '1.0' ) 
  169. curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); 
  170. else 
  171. curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 ); 
  172.  
  173. /** 
  174. * Fires before the cURL request is executed. 
  175. * Cookies are not currently handled by the HTTP API. This action allows 
  176. * plugins to handle cookies themselves. 
  177. * @since 2.8.0 
  178. * @param resource &$handle The cURL handle returned by curl_init(). 
  179. * @param array $r The HTTP request arguments. 
  180. * @param string $url The request URL. 
  181. */ 
  182. do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) ); 
  183.  
  184. // We don't need to return the body, so don't. Just execute request and return. 
  185. if ( ! $r['blocking'] ) { 
  186. curl_exec( $handle ); 
  187.  
  188. if ( $curl_error = curl_error( $handle ) ) { 
  189. curl_close( $handle ); 
  190. return new WP_Error( 'http_request_failed', $curl_error ); 
  191. if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { 
  192. curl_close( $handle ); 
  193. return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); 
  194.  
  195. curl_close( $handle ); 
  196. return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 
  197.  
  198. curl_exec( $handle ); 
  199. $theHeaders = WP_Http::processHeaders( $this->headers, $url ); 
  200. $theBody = $this->body; 
  201. $bytes_written_total = $this->bytes_written_total; 
  202.  
  203. $this->headers = ''; 
  204. $this->body = ''; 
  205. $this->bytes_written_total = 0; 
  206.  
  207. $curl_error = curl_errno( $handle ); 
  208.  
  209. // If an error occurred, or, no response. 
  210. if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) { 
  211. if ( CURLE_WRITE_ERROR /** 23 */ == $curl_error ) { 
  212. if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) { 
  213. if ( $r['stream'] ) { 
  214. curl_close( $handle ); 
  215. fclose( $this->stream_handle ); 
  216. return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) ); 
  217. } else { 
  218. curl_close( $handle ); 
  219. return new WP_Error( 'http_request_failed', curl_error( $handle ) ); 
  220. } else { 
  221. if ( $curl_error = curl_error( $handle ) ) { 
  222. curl_close( $handle ); 
  223. return new WP_Error( 'http_request_failed', $curl_error ); 
  224. if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) { 
  225. curl_close( $handle ); 
  226. return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); 
  227.  
  228. curl_close( $handle ); 
  229.  
  230. if ( $r['stream'] ) 
  231. fclose( $this->stream_handle ); 
  232.  
  233. $response = array( 
  234. 'headers' => $theHeaders['headers'],  
  235. 'body' => null,  
  236. 'response' => $theHeaders['response'],  
  237. 'cookies' => $theHeaders['cookies'],  
  238. 'filename' => $r['filename'] 
  239. ); 
  240.  
  241. // Handle redirects. 
  242. if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) ) 
  243. return $redirect_response; 
  244.  
  245. if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) ) 
  246. $theBody = WP_Http_Encoding::decompress( $theBody ); 
  247.  
  248. $response['body'] = $theBody; 
  249.  
  250. return $response; 
  251.  
  252. /** 
  253. * Grabs the headers of the cURL request. 
  254. * Each header is sent individually to this callback, so we append to the `$header` property 
  255. * for temporary storage 
  256. * @since 3.2.0 
  257. * @access private 
  258. * @param resource $handle cURL handle. 
  259. * @param string $headers cURL request headers. 
  260. * @return int Length of the request headers. 
  261. */ 
  262. private function stream_headers( $handle, $headers ) { 
  263. $this->headers .= $headers; 
  264. return strlen( $headers ); 
  265.  
  266. /** 
  267. * Grabs the body of the cURL request. 
  268. * The contents of the document are passed in chunks, so we append to the `$body` 
  269. * property for temporary storage. Returning a length shorter than the length of 
  270. * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`. 
  271. * @since 3.6.0 
  272. * @access private 
  273. * @param resource $handle cURL handle. 
  274. * @param string $data cURL request body. 
  275. * @return int Total bytes of data written. 
  276. */ 
  277. private function stream_body( $handle, $data ) { 
  278. $data_length = strlen( $data ); 
  279.  
  280. if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) { 
  281. $data_length = ( $this->max_body_length - $this->bytes_written_total ); 
  282. $data = substr( $data, 0, $data_length ); 
  283.  
  284. if ( $this->stream_handle ) { 
  285. $bytes_written = fwrite( $this->stream_handle, $data ); 
  286. } else { 
  287. $this->body .= $data; 
  288. $bytes_written = $data_length; 
  289.  
  290. $this->bytes_written_total += $bytes_written; 
  291.  
  292. // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR. 
  293. return $bytes_written; 
  294.  
  295. /** 
  296. * Determines whether this class can be used for retrieving a URL. 
  297. * @static 
  298. * @since 2.7.0 
  299. * @param array $args Optional. Array of request arguments. Default empty array. 
  300. * @return bool False means this class can not be used, true means it can. 
  301. */ 
  302. public static function test( $args = array() ) { 
  303. if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) ) 
  304. return false; 
  305.  
  306. $is_ssl = isset( $args['ssl'] ) && $args['ssl']; 
  307.  
  308. if ( $is_ssl ) { 
  309. $curl_version = curl_version(); 
  310. // Check whether this cURL version support SSL requests. 
  311. if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) 
  312. return false; 
  313.  
  314. /** 
  315. * Filters whether cURL can be used as a transport for retrieving a URL. 
  316. * @since 2.7.0 
  317. * @param bool $use_class Whether the class can be used. Default true. 
  318. * @param array $args An array of request arguments. 
  319. */ 
  320. return apply_filters( 'use_curl_transport', true, $args );