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