PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/class-wp-http-curl.php

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