Make WordPress Core

source: trunk/src/wp-includes/class-wp-http-curl.php

Last change on this file was 60703, checked in by SergeyBiryukov, 4 months ago

Code Modernization: Address no-op function deprecations in PHP 8.5.

Several PHP functions that have not been doing anything since PHP 8.0/8.1, specifically:

  • finfo_close() since the ext/fileinfo migration in PHP 8.1
  • xml_parser_free() since the ext/xml migration in PHP 8.0
  • curl_close() since the ext/curl migration in PHP 8.0
  • curl_share_close() since the ext/curl migration in PHP 8.0
  • imagedestroy() since the ext/gd migration in PHP 8.0

will be deprecated in PHP 8.5 and will thus be throwing warnings.

This commit adds conditional checks to only call these functions on the relevant PHP versions.

Reference: PHP RFC: Deprecations for PHP 8.5: Deprecate no-op functions from the resource to object conversion.

Props TobiasBg, SergeyBiryukov.
See #63061.

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