]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-http.php
WordPress 4.0
[autoinstalls/wordpress.git] / wp-includes / class-http.php
1 <?php
2 /**
3  * Simple and uniform HTTP request API.
4  *
5  * Standardizes the HTTP requests for WordPress. Handles cookies, gzip encoding and decoding, chunk
6  * decoding, if HTTP 1.1 and various other difficult HTTP protocol implementations.
7  *
8  * @link http://trac.wordpress.org/ticket/4779 HTTP API Proposal
9  *
10  * @package WordPress
11  * @subpackage HTTP
12  * @since 2.7.0
13  */
14
15 /**
16  * WordPress HTTP Class for managing HTTP Transports and making HTTP requests.
17  *
18  * This class is used to consistently make outgoing HTTP requests easy for developers
19  * while still being compatible with the many PHP configurations under which
20  * WordPress runs.
21  *
22  * Debugging includes several actions, which pass different variables for debugging the HTTP API.
23  *
24  * @package WordPress
25  * @subpackage HTTP
26  * @since 2.7.0
27  */
28 class WP_Http {
29
30         /**
31          * Send an HTTP request to a URI.
32          *
33          * Please note: The only URI that are supported in the HTTP Transport implementation
34          * are the HTTP and HTTPS protocols.
35          *
36          * @access public
37          * @since 2.7.0
38          *
39          * @param string       $url  The request URL.
40          * @param string|array $args {
41          *     Optional. Array or string of HTTP request arguments.
42          *
43          *     @type string       $method              Request method. Accepts 'GET', 'POST', 'HEAD', or 'PUT'.
44          *                                             Some transports technically allow others, but should not be
45          *                                             assumed. Default 'GET'.
46          *     @type int          $timeout             How long the connection should stay open in seconds. Default 5.
47          *     @type int          $redirection         Number of allowed redirects. Not supported by all transports
48          *                                             Default 5.
49          *     @type string       $httpversion         Version of the HTTP protocol to use. Accepts '1.0' and '1.1'.
50          *                                             Default '1.0'.
51          *     @type string       $user-agent          User-agent value sent.
52          *                                             Default WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ).
53          *     @type bool         $reject_unsafe_urls  Whether to pass URLs through {@see wp_http_validate_url()}.
54          *                                             Default false.
55          *     @type bool         $blocking            Whether the calling code requires the result of the request.
56          *                                             If set to false, the request will be sent to the remote server,
57          *                                             and processing returned to the calling code immediately, the caller
58          *                                             will know if the request succeeded or failed, but will not receive
59          *                                             any response from the remote server. Default true.
60          *     @type string|array $headers             Array or string of headers to send with the request.
61          *                                             Default empty array.
62          *     @type array        $cookies             List of cookies to send with the request. Default empty array.
63          *     @type string|array $body                Body to send with the request. Default null.
64          *     @type bool         $compress            Whether to compress the $body when sending the request.
65          *                                             Default false.
66          *     @type bool         $decompress          Whether to decompress a compressed response. If set to false and
67          *                                             compressed content is returned in the response anyway, it will
68          *                                             need to be separately decompressed. Default true.
69          *     @type bool         $sslverify           Whether to verify SSL for the request. Default true.
70          *     @type string       sslcertificates      Absolute path to an SSL certificate .crt file.
71          *                                             Default ABSPATH . WPINC . '/certificates/ca-bundle.crt'.
72          *     @type bool         $stream              Whether to stream to a file. If set to true and no filename was
73          *                                             given, it will be droped it in the WP temp dir and its name will
74          *                                             be set using the basename of the URL. Default false.
75          *     @type string       $filename            Filename of the file to write to when streaming. $stream must be
76          *                                             set to true. Default null.
77          *     @type int          $limit_response_size Size in bytes to limit the response to. Default null.
78          *
79          * }
80          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'.
81          *                        A WP_Error instance upon error.
82          */
83         public function request( $url, $args = array() ) {
84                 global $wp_version;
85
86                 $defaults = array(
87                         'method' => 'GET',
88                         /**
89                          * Filter the timeout value for an HTTP request.
90                          *
91                          * @since 2.7.0
92                          *
93                          * @param int $timeout_value Time in seconds until a request times out.
94                          *                           Default 5.
95                          */
96                         'timeout' => apply_filters( 'http_request_timeout', 5 ),
97                         /**
98                          * Filter the number of redirects allowed during an HTTP request.
99                          *
100                          * @since 2.7.0
101                          *
102                          * @param int $redirect_count Number of redirects allowed. Default 5.
103                          */
104                         'redirection' => apply_filters( 'http_request_redirection_count', 5 ),
105                         /**
106                          * Filter the version of the HTTP protocol used in a request.
107                          *
108                          * @since 2.7.0
109                          *
110                          * @param string $version Version of HTTP used. Accepts '1.0' and '1.1'.
111                          *                        Default '1.0'.
112                          */
113                         'httpversion' => apply_filters( 'http_request_version', '1.0' ),
114                         /**
115                          * Filter the user agent value sent with an HTTP request.
116                          *
117                          * @since 2.7.0
118                          *
119                          * @param string $user_agent WordPress user agent string.
120                          */
121                         'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' ) ),
122                         /**
123                          * Filter whether to pass URLs through wp_http_validate_url() in an HTTP request.
124                          *
125                          * @since 3.6.0
126                          *
127                          * @param bool $pass_url Whether to pass URLs through wp_http_validate_url().
128                          *                       Default false.
129                          */
130                         'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false ),
131                         'blocking' => true,
132                         'headers' => array(),
133                         'cookies' => array(),
134                         'body' => null,
135                         'compress' => false,
136                         'decompress' => true,
137                         'sslverify' => true,
138                         'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
139                         'stream' => false,
140                         'filename' => null,
141                         'limit_response_size' => null,
142                 );
143
144                 // Pre-parse for the HEAD checks.
145                 $args = wp_parse_args( $args );
146
147                 // By default, Head requests do not cause redirections.
148                 if ( isset($args['method']) && 'HEAD' == $args['method'] )
149                         $defaults['redirection'] = 0;
150
151                 $r = wp_parse_args( $args, $defaults );
152                 /**
153                  * Filter the arguments used in an HTTP request.
154                  *
155                  * @since 2.7.0
156                  *
157                  * @param array  $r   An array of HTTP request arguments.
158                  * @param string $url The request URL.
159                  */
160                 $r = apply_filters( 'http_request_args', $r, $url );
161
162                 // The transports decrement this, store a copy of the original value for loop purposes.
163                 if ( ! isset( $r['_redirection'] ) )
164                         $r['_redirection'] = $r['redirection'];
165
166                 /**
167                  * Filter whether to preempt an HTTP request's return.
168                  *
169                  * Returning a truthy value to the filter will short-circuit
170                  * the HTTP request and return early with that value.
171                  *
172                  * @since 2.9.0
173                  *
174                  * @param bool   $preempt Whether to preempt an HTTP request return. Default false.
175                  * @param array  $r       HTTP request arguments.
176                  * @param string $url     The request URL.
177                  */
178                 $pre = apply_filters( 'pre_http_request', false, $r, $url );
179                 if ( false !== $pre )
180                         return $pre;
181
182                 if ( function_exists( 'wp_kses_bad_protocol' ) ) {
183                         if ( $r['reject_unsafe_urls'] )
184                                 $url = wp_http_validate_url( $url );
185                         $url = wp_kses_bad_protocol( $url, array( 'http', 'https', 'ssl' ) );
186                 }
187
188                 $arrURL = @parse_url( $url );
189
190                 if ( empty( $url ) || empty( $arrURL['scheme'] ) )
191                         return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
192
193                 if ( $this->block_request( $url ) )
194                         return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
195
196                 /*
197                  * Determine if this is a https call and pass that on to the transport functions
198                  * so that we can blacklist the transports that do not support ssl verification
199                  */
200                 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
201
202                 // Determine if this request is to OUR install of WordPress.
203                 $homeURL = parse_url( get_bloginfo( 'url' ) );
204                 $r['local'] = 'localhost' == $arrURL['host'] || ( isset( $homeURL['host'] ) && $homeURL['host'] == $arrURL['host'] );
205                 unset( $homeURL );
206
207                 /*
208                  * If we are streaming to a file but no filename was given drop it in the WP temp dir
209                  * and pick its name using the basename of the $url.
210                  */
211                 if ( $r['stream']  && empty( $r['filename'] ) )
212                         $r['filename'] = get_temp_dir() . basename( $url );
213
214                 /*
215                  * Force some settings if we are streaming to a file and check for existence and perms
216                  * of destination directory.
217                  */
218                 if ( $r['stream'] ) {
219                         $r['blocking'] = true;
220                         if ( ! wp_is_writable( dirname( $r['filename'] ) ) )
221                                 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
222                 }
223
224                 if ( is_null( $r['headers'] ) )
225                         $r['headers'] = array();
226
227                 if ( ! is_array( $r['headers'] ) ) {
228                         $processedHeaders = WP_Http::processHeaders( $r['headers'], $url );
229                         $r['headers'] = $processedHeaders['headers'];
230                 }
231
232                 if ( isset( $r['headers']['User-Agent'] ) ) {
233                         $r['user-agent'] = $r['headers']['User-Agent'];
234                         unset( $r['headers']['User-Agent'] );
235                 }
236
237                 if ( isset( $r['headers']['user-agent'] ) ) {
238                         $r['user-agent'] = $r['headers']['user-agent'];
239                         unset( $r['headers']['user-agent'] );
240                 }
241
242                 if ( '1.1' == $r['httpversion'] && !isset( $r['headers']['connection'] ) ) {
243                         $r['headers']['connection'] = 'close';
244                 }
245
246                 // Construct Cookie: header if any cookies are set.
247                 WP_Http::buildCookieHeader( $r );
248
249                 // Avoid issues where mbstring.func_overload is enabled.
250                 mbstring_binary_safe_encoding();
251
252                 if ( ! isset( $r['headers']['Accept-Encoding'] ) ) {
253                         if ( $encoding = WP_Http_Encoding::accept_encoding( $url, $r ) )
254                                 $r['headers']['Accept-Encoding'] = $encoding;
255                 }
256
257                 if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
258                         if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
259                                 $r['body'] = http_build_query( $r['body'], null, '&' );
260
261                                 if ( ! isset( $r['headers']['Content-Type'] ) )
262                                         $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
263                         }
264
265                         if ( '' === $r['body'] )
266                                 $r['body'] = null;
267
268                         if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
269                                 $r['headers']['Content-Length'] = strlen( $r['body'] );
270                 }
271
272                 $response = $this->_dispatch_request( $url, $r );
273
274                 reset_mbstring_encoding();
275
276                 if ( is_wp_error( $response ) )
277                         return $response;
278
279                 // Append cookies that were used in this request to the response
280                 if ( ! empty( $r['cookies'] ) ) {
281                         $cookies_set = wp_list_pluck( $response['cookies'], 'name' );
282                         foreach ( $r['cookies'] as $cookie ) {
283                                 if ( ! in_array( $cookie->name, $cookies_set ) && $cookie->test( $url ) ) {
284                                         $response['cookies'][] = $cookie;
285                                 }
286                         }
287                 }
288
289                 return $response;
290         }
291
292         /**
293          * Tests which transports are capable of supporting the request.
294          *
295          * @since 3.2.0
296          * @access private
297          *
298          * @param array $args Request arguments
299          * @param string $url URL to Request
300          *
301          * @return string|bool Class name for the first transport that claims to support the request. False if no transport claims to support the request.
302          */
303         public function _get_first_available_transport( $args, $url = null ) {
304                 /**
305                  * Filter which HTTP transports are available and in what order.
306                  *
307                  * @since 3.7.0
308                  *
309                  * @param array  $value Array of HTTP transports to check. Default array contains
310                  *                      'curl', and 'streams', in that order.
311                  * @param array  $args  HTTP request arguments.
312                  * @param string $url   The URL to request.
313                  */
314                 $request_order = apply_filters( 'http_api_transports', array( 'curl', 'streams' ), $args, $url );
315
316                 // Loop over each transport on each HTTP request looking for one which will serve this request's needs.
317                 foreach ( $request_order as $transport ) {
318                         $class = 'WP_HTTP_' . $transport;
319
320                         // Check to see if this transport is a possibility, calls the transport statically.
321                         if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
322                                 continue;
323
324                         return $class;
325                 }
326
327                 return false;
328         }
329
330         /**
331          * Dispatches a HTTP request to a supporting transport.
332          *
333          * Tests each transport in order to find a transport which matches the request arguments.
334          * Also caches the transport instance to be used later.
335          *
336          * The order for requests is cURL, and then PHP Streams.
337          *
338          * @since 3.2.0
339          * @access private
340          *
341          * @param string $url URL to Request
342          * @param array $args Request arguments
343          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
344          */
345         private function _dispatch_request( $url, $args ) {
346                 static $transports = array();
347
348                 $class = $this->_get_first_available_transport( $args, $url );
349                 if ( !$class )
350                         return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
351
352                 // Transport claims to support request, instantiate it and give it a whirl.
353                 if ( empty( $transports[$class] ) )
354                         $transports[$class] = new $class;
355
356                 $response = $transports[$class]->request( $url, $args );
357
358                 /**
359                  * Fires after an HTTP API response is received and before the response is returned.
360                  *
361                  * @since 2.8.0
362                  *
363                  * @param array|WP_Error $response HTTP response or WP_Error object.
364                  * @param string         $context  Context under which the hook is fired.
365                  * @param string         $class    HTTP transport used.
366                  * @param array          $args     HTTP request arguments.
367                  * @param string         $url      The request URL.
368                  */
369                 do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
370
371                 if ( is_wp_error( $response ) )
372                         return $response;
373
374                 /**
375                  * Filter the HTTP API response immediately before the response is returned.
376                  *
377                  * @since 2.9.0
378                  *
379                  * @param array  $response HTTP response.
380                  * @param array  $args     HTTP request arguments.
381                  * @param string $url      The request URL.
382                  */
383                 return apply_filters( 'http_response', $response, $args, $url );
384         }
385
386         /**
387          * Uses the POST HTTP method.
388          *
389          * Used for sending data that is expected to be in the body.
390          *
391          * @access public
392          * @since 2.7.0
393          *
394          * @param string       $url  The request URL.
395          * @param string|array $args Optional. Override the defaults.
396          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
397          */
398         public function post($url, $args = array()) {
399                 $defaults = array('method' => 'POST');
400                 $r = wp_parse_args( $args, $defaults );
401                 return $this->request($url, $r);
402         }
403
404         /**
405          * Uses the GET HTTP method.
406          *
407          * Used for sending data that is expected to be in the body.
408          *
409          * @access public
410          * @since 2.7.0
411          *
412          * @param string $url The request URL.
413          * @param string|array $args Optional. Override the defaults.
414          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
415          */
416         public function get($url, $args = array()) {
417                 $defaults = array('method' => 'GET');
418                 $r = wp_parse_args( $args, $defaults );
419                 return $this->request($url, $r);
420         }
421
422         /**
423          * Uses the HEAD HTTP method.
424          *
425          * Used for sending data that is expected to be in the body.
426          *
427          * @access public
428          * @since 2.7.0
429          *
430          * @param string $url The request URL.
431          * @param string|array $args Optional. Override the defaults.
432          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
433          */
434         public function head($url, $args = array()) {
435                 $defaults = array('method' => 'HEAD');
436                 $r = wp_parse_args( $args, $defaults );
437                 return $this->request($url, $r);
438         }
439
440         /**
441          * Parses the responses and splits the parts into headers and body.
442          *
443          * @access public
444          * @static
445          * @since 2.7.0
446          *
447          * @param string $strResponse The full response string
448          * @return array Array with 'headers' and 'body' keys.
449          */
450         public static function processResponse($strResponse) {
451                 $res = explode("\r\n\r\n", $strResponse, 2);
452
453                 return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
454         }
455
456         /**
457          * Transform header string into an array.
458          *
459          * If an array is given then it is assumed to be raw header data with numeric keys with the
460          * headers as the values. No headers must be passed that were already processed.
461          *
462          * @access public
463          * @static
464          * @since 2.7.0
465          *
466          * @param string|array $headers
467          * @param string $url The URL that was requested
468          * @return array Processed string headers. If duplicate headers are encountered,
469          *                                      Then a numbered array is returned as the value of that header-key.
470          */
471         public static function processHeaders( $headers, $url = '' ) {
472                 // Split headers, one per array element.
473                 if ( is_string($headers) ) {
474                         // Tolerate line terminator: CRLF = LF (RFC 2616 19.3).
475                         $headers = str_replace("\r\n", "\n", $headers);
476                         /*
477                          * Unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>,
478                          * <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2).
479                          */
480                         $headers = preg_replace('/\n[ \t]/', ' ', $headers);
481                         // Create the headers array.
482                         $headers = explode("\n", $headers);
483                 }
484
485                 $response = array('code' => 0, 'message' => '');
486
487                 /*
488                  * If a redirection has taken place, The headers for each page request may have been passed.
489                  * In this case, determine the final HTTP header and parse from there.
490                  */
491                 for ( $i = count($headers)-1; $i >= 0; $i-- ) {
492                         if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) {
493                                 $headers = array_splice($headers, $i);
494                                 break;
495                         }
496                 }
497
498                 $cookies = array();
499                 $newheaders = array();
500                 foreach ( (array) $headers as $tempheader ) {
501                         if ( empty($tempheader) )
502                                 continue;
503
504                         if ( false === strpos($tempheader, ':') ) {
505                                 $stack = explode(' ', $tempheader, 3);
506                                 $stack[] = '';
507                                 list( , $response['code'], $response['message']) = $stack;
508                                 continue;
509                         }
510
511                         list($key, $value) = explode(':', $tempheader, 2);
512
513                         $key = strtolower( $key );
514                         $value = trim( $value );
515
516                         if ( isset( $newheaders[ $key ] ) ) {
517                                 if ( ! is_array( $newheaders[ $key ] ) )
518                                         $newheaders[$key] = array( $newheaders[ $key ] );
519                                 $newheaders[ $key ][] = $value;
520                         } else {
521                                 $newheaders[ $key ] = $value;
522                         }
523                         if ( 'set-cookie' == $key )
524                                 $cookies[] = new WP_Http_Cookie( $value, $url );
525                 }
526
527                 return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
528         }
529
530         /**
531          * Takes the arguments for a ::request() and checks for the cookie array.
532          *
533          * If it's found, then it upgrades any basic name => value pairs to WP_Http_Cookie instances,
534          * which are each parsed into strings and added to the Cookie: header (within the arguments array).
535          * Edits the array by reference.
536          *
537          * @access public
538          * @version 2.8.0
539          * @static
540          *
541          * @param array $r Full array of args passed into ::request()
542          */
543         public static function buildCookieHeader( &$r ) {
544                 if ( ! empty($r['cookies']) ) {
545                         // Upgrade any name => value cookie pairs to WP_HTTP_Cookie instances.
546                         foreach ( $r['cookies'] as $name => $value ) {
547                                 if ( ! is_object( $value ) )
548                                         $r['cookies'][ $name ] = new WP_HTTP_Cookie( array( 'name' => $name, 'value' => $value ) );
549                         }
550
551                         $cookies_header = '';
552                         foreach ( (array) $r['cookies'] as $cookie ) {
553                                 $cookies_header .= $cookie->getHeaderValue() . '; ';
554                         }
555
556                         $cookies_header = substr( $cookies_header, 0, -2 );
557                         $r['headers']['cookie'] = $cookies_header;
558                 }
559         }
560
561         /**
562          * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
563          *
564          * Based off the HTTP http_encoding_dechunk function.
565          *
566          * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
567          *
568          * @access public
569          * @since 2.7.0
570          * @static
571          *
572          * @param string $body Body content
573          * @return string Chunked decoded body on success or raw body on failure.
574          */
575         public static function chunkTransferDecode( $body ) {
576                 // The body is not chunked encoded or is malformed.
577                 if ( ! preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', trim( $body ) ) )
578                         return $body;
579
580                 $parsed_body = '';
581
582                 // We'll be altering $body, so need a backup in case of error.
583                 $body_original = $body;
584
585                 while ( true ) {
586                         $has_chunk = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $body, $match );
587                         if ( ! $has_chunk || empty( $match[1] ) )
588                                 return $body_original;
589
590                         $length = hexdec( $match[1] );
591                         $chunk_length = strlen( $match[0] );
592
593                         // Parse out the chunk of data.
594                         $parsed_body .= substr( $body, $chunk_length, $length );
595
596                         // Remove the chunk from the raw data.
597                         $body = substr( $body, $length + $chunk_length );
598
599                         // End of the document.
600                         if ( '0' === trim( $body ) )
601                                 return $parsed_body;
602                 }
603         }
604
605         /**
606          * Block requests through the proxy.
607          *
608          * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
609          * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
610          *
611          * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
612          * file and this will only allow localhost and your blog to make requests. The constant
613          * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
614          * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
615          * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
616          *
617          * @since 2.8.0
618          * @link http://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
619          * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
620          *
621          * @param string $uri URI of url.
622          * @return bool True to block, false to allow.
623          */
624         public function block_request($uri) {
625                 // We don't need to block requests, because nothing is blocked.
626                 if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL )
627                         return false;
628
629                 $check = parse_url($uri);
630                 if ( ! $check )
631                         return true;
632
633                 $home = parse_url( get_option('siteurl') );
634
635                 // Don't block requests back to ourselves by default.
636                 if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) ) {
637                         /**
638                          * Filter whether to block local requests through the proxy.
639                          *
640                          * @since 2.8.0
641                          *
642                          * @param bool $block Whether to block local requests through proxy.
643                          *                    Default false.
644                          */
645                         return apply_filters( 'block_local_requests', false );
646                 }
647
648                 if ( !defined('WP_ACCESSIBLE_HOSTS') )
649                         return true;
650
651                 static $accessible_hosts;
652                 static $wildcard_regex = false;
653                 if ( null == $accessible_hosts ) {
654                         $accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS);
655
656                         if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) {
657                                 $wildcard_regex = array();
658                                 foreach ( $accessible_hosts as $host )
659                                         $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
660                                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
661                         }
662                 }
663
664                 if ( !empty($wildcard_regex) )
665                         return !preg_match($wildcard_regex, $check['host']);
666                 else
667                         return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If it's in the array, then we can't access it.
668
669         }
670
671         public static function make_absolute_url( $maybe_relative_path, $url ) {
672                 if ( empty( $url ) )
673                         return $maybe_relative_path;
674
675                 // Check for a scheme.
676                 if ( false !== strpos( $maybe_relative_path, '://' ) )
677                         return $maybe_relative_path;
678
679                 if ( ! $url_parts = @parse_url( $url ) )
680                         return $maybe_relative_path;
681
682                 if ( ! $relative_url_parts = @parse_url( $maybe_relative_path ) )
683                         return $maybe_relative_path;
684
685                 $absolute_path = $url_parts['scheme'] . '://' . $url_parts['host'];
686                 if ( isset( $url_parts['port'] ) )
687                         $absolute_path .= ':' . $url_parts['port'];
688
689                 // Start off with the Absolute URL path.
690                 $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
691
692                 // If it's a root-relative path, then great.
693                 if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) {
694                         $path = $relative_url_parts['path'];
695
696                 // Else it's a relative path.
697                 } elseif ( ! empty( $relative_url_parts['path'] ) ) {
698                         // Strip off any file components from the absolute path.
699                         $path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
700
701                         // Build the new path.
702                         $path .= $relative_url_parts['path'];
703
704                         // Strip all /path/../ out of the path.
705                         while ( strpos( $path, '../' ) > 1 ) {
706                                 $path = preg_replace( '![^/]+/\.\./!', '', $path );
707                         }
708
709                         // Strip any final leading ../ from the path.
710                         $path = preg_replace( '!^/(\.\./)+!', '', $path );
711                 }
712
713                 // Add the Query string.
714                 if ( ! empty( $relative_url_parts['query'] ) )
715                         $path .= '?' . $relative_url_parts['query'];
716
717                 return $absolute_path . '/' . ltrim( $path, '/' );
718         }
719
720         /**
721          * Handles HTTP Redirects and follows them if appropriate.
722          *
723          * @since 3.7.0
724          *
725          * @param string $url The URL which was requested.
726          * @param array $args The Arguments which were used to make the request.
727          * @param array $response The Response of the HTTP request.
728          * @return false|object False if no redirect is present, a WP_HTTP or WP_Error result otherwise.
729          */
730         public static function handle_redirects( $url, $args, $response ) {
731                 // If no redirects are present, or, redirects were not requested, perform no action.
732                 if ( ! isset( $response['headers']['location'] ) || 0 === $args['_redirection'] )
733                         return false;
734
735                 // Only perform redirections on redirection http codes.
736                 if ( $response['response']['code'] > 399 || $response['response']['code'] < 300 )
737                         return false;
738
739                 // Don't redirect if we've run out of redirects.
740                 if ( $args['redirection']-- <= 0 )
741                         return new WP_Error( 'http_request_failed', __('Too many redirects.') );
742
743                 $redirect_location = $response['headers']['location'];
744
745                 // If there were multiple Location headers, use the last header specified.
746                 if ( is_array( $redirect_location ) )
747                         $redirect_location = array_pop( $redirect_location );
748
749                 $redirect_location = WP_HTTP::make_absolute_url( $redirect_location, $url );
750
751                 // POST requests should not POST to a redirected location.
752                 if ( 'POST' == $args['method'] ) {
753                         if ( in_array( $response['response']['code'], array( 302, 303 ) ) )
754                                 $args['method'] = 'GET';
755                 }
756
757                 // Include valid cookies in the redirect process.
758                 if ( ! empty( $response['cookies'] ) ) {
759                         foreach ( $response['cookies'] as $cookie ) {
760                                 if ( $cookie->test( $redirect_location ) )
761                                         $args['cookies'][] = $cookie;
762                         }
763                 }
764
765                 return wp_remote_request( $redirect_location, $args );
766         }
767
768         /**
769          * Determines if a specified string represents an IP address or not.
770          *
771          * This function also detects the type of the IP address, returning either
772          * '4' or '6' to represent a IPv4 and IPv6 address respectively.
773          * This does not verify if the IP is a valid IP, only that it appears to be
774          * an IP address.
775          *
776          * @see http://home.deds.nl/~aeron/regex/ for IPv6 regex
777          *
778          * @since 3.7.0
779          * @static
780          *
781          * @param string $maybe_ip A suspected IP address
782          * @return integer|bool Upon success, '4' or '6' to represent a IPv4 or IPv6 address, false upon failure
783          */
784         public static function is_ip_address( $maybe_ip ) {
785                 if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $maybe_ip ) )
786                         return 4;
787
788                 if ( false !== strpos( $maybe_ip, ':' ) && preg_match( '/^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i', trim( $maybe_ip, ' []' ) ) )
789                         return 6;
790
791                 return false;
792         }
793
794 }
795
796 /**
797  * HTTP request method uses PHP Streams to retrieve the url.
798  *
799  * @since 2.7.0
800  * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
801  */
802 class WP_Http_Streams {
803         /**
804          * Send a HTTP request to a URI using PHP Streams.
805          *
806          * @see WP_Http::request For default options descriptions.
807          *
808          * @since 2.7.0
809          * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
810          *
811          * @access public
812          * @param string $url The request URL.
813          * @param string|array $args Optional. Override the defaults.
814          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
815          */
816         public function request($url, $args = array()) {
817                 $defaults = array(
818                         'method' => 'GET', 'timeout' => 5,
819                         'redirection' => 5, 'httpversion' => '1.0',
820                         'blocking' => true,
821                         'headers' => array(), 'body' => null, 'cookies' => array()
822                 );
823
824                 $r = wp_parse_args( $args, $defaults );
825
826                 if ( isset($r['headers']['User-Agent']) ) {
827                         $r['user-agent'] = $r['headers']['User-Agent'];
828                         unset($r['headers']['User-Agent']);
829                 } else if ( isset($r['headers']['user-agent']) ) {
830                         $r['user-agent'] = $r['headers']['user-agent'];
831                         unset($r['headers']['user-agent']);
832                 }
833
834                 // Construct Cookie: header if any cookies are set.
835                 WP_Http::buildCookieHeader( $r );
836
837                 $arrURL = parse_url($url);
838
839                 $connect_host = $arrURL['host'];
840
841                 $secure_transport = ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' );
842                 if ( ! isset( $arrURL['port'] ) ) {
843                         if ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) {
844                                 $arrURL['port'] = 443;
845                                 $secure_transport = true;
846                         } else {
847                                 $arrURL['port'] = 80;
848                         }
849                 }
850
851                 if ( isset( $r['headers']['Host'] ) || isset( $r['headers']['host'] ) ) {
852                         if ( isset( $r['headers']['Host'] ) )
853                                 $arrURL['host'] = $r['headers']['Host'];
854                         else
855                                 $arrURL['host'] = $r['headers']['host'];
856                         unset( $r['headers']['Host'], $r['headers']['host'] );
857                 }
858
859                 /*
860                  * Certain versions of PHP have issues with 'localhost' and IPv6, It attempts to connect
861                  * to ::1, which fails when the server is not set up for it. For compatibility, always
862                  * connect to the IPv4 address.
863                  */
864                 if ( 'localhost' == strtolower( $connect_host ) )
865                         $connect_host = '127.0.0.1';
866
867                 $connect_host = $secure_transport ? 'ssl://' . $connect_host : 'tcp://' . $connect_host;
868
869                 $is_local = isset( $r['local'] ) && $r['local'];
870                 $ssl_verify = isset( $r['sslverify'] ) && $r['sslverify'];
871                 if ( $is_local ) {
872                         /**
873                          * Filter whether SSL should be verified for local requests.
874                          *
875                          * @since 2.8.0
876                          *
877                          * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
878                          */
879                         $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
880                 } elseif ( ! $is_local ) {
881                         /**
882                          * Filter whether SSL should be verified for non-local requests.
883                          *
884                          * @since 2.8.0
885                          *
886                          * @param bool $ssl_verify Whether to verify the SSL connection. Default true.
887                          */
888                         $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
889                 }
890
891                 $proxy = new WP_HTTP_Proxy();
892
893                 $context = stream_context_create( array(
894                         'ssl' => array(
895                                 'verify_peer' => $ssl_verify,
896                                 //'CN_match' => $arrURL['host'], // This is handled by self::verify_ssl_certificate()
897                                 'capture_peer_cert' => $ssl_verify,
898                                 'SNI_enabled' => true,
899                                 'cafile' => $r['sslcertificates'],
900                                 'allow_self_signed' => ! $ssl_verify,
901                         )
902                 ) );
903
904                 $timeout = (int) floor( $r['timeout'] );
905                 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
906                 $connect_timeout = max( $timeout, 1 );
907
908                 // Store error number.
909                 $connection_error = null;
910
911                 // Store error string.
912                 $connection_error_str = null;
913
914                 if ( !WP_DEBUG ) {
915                         // In the event that the SSL connection fails, silence the many PHP Warnings.
916                         if ( $secure_transport )
917                                 $error_reporting = error_reporting(0);
918
919                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
920                                 $handle = @stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
921                         else
922                                 $handle = @stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
923
924                         if ( $secure_transport )
925                                 error_reporting( $error_reporting );
926
927                 } else {
928                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
929                                 $handle = stream_socket_client( 'tcp://' . $proxy->host() . ':' . $proxy->port(), $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
930                         else
931                                 $handle = stream_socket_client( $connect_host . ':' . $arrURL['port'], $connection_error, $connection_error_str, $connect_timeout, STREAM_CLIENT_CONNECT, $context );
932                 }
933
934                 if ( false === $handle ) {
935                         // SSL connection failed due to expired/invalid cert, or, OpenSSL configuration is broken.
936                         if ( $secure_transport && 0 === $connection_error && '' === $connection_error_str )
937                                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
938
939                         return new WP_Error('http_request_failed', $connection_error . ': ' . $connection_error_str );
940                 }
941
942                 // Verify that the SSL certificate is valid for this request.
943                 if ( $secure_transport && $ssl_verify && ! $proxy->is_enabled() ) {
944                         if ( ! self::verify_ssl_certificate( $handle, $arrURL['host'] ) )
945                                 return new WP_Error( 'http_request_failed', __( 'The SSL certificate for the host could not be verified.' ) );
946                 }
947
948                 stream_set_timeout( $handle, $timeout, $utimeout );
949
950                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
951                         $requestPath = $url;
952                 else
953                         $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
954
955                 if ( empty($requestPath) )
956                         $requestPath .= '/';
957
958                 $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
959
960                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
961                         $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
962                 else
963                         $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
964
965                 if ( isset($r['user-agent']) )
966                         $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
967
968                 if ( is_array($r['headers']) ) {
969                         foreach ( (array) $r['headers'] as $header => $headerValue )
970                                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
971                 } else {
972                         $strHeaders .= $r['headers'];
973                 }
974
975                 if ( $proxy->use_authentication() )
976                         $strHeaders .= $proxy->authentication_header() . "\r\n";
977
978                 $strHeaders .= "\r\n";
979
980                 if ( ! is_null($r['body']) )
981                         $strHeaders .= $r['body'];
982
983                 fwrite($handle, $strHeaders);
984
985                 if ( ! $r['blocking'] ) {
986                         stream_set_blocking( $handle, 0 );
987                         fclose( $handle );
988                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
989                 }
990
991                 $strResponse = '';
992                 $bodyStarted = false;
993                 $keep_reading = true;
994                 $block_size = 4096;
995                 if ( isset( $r['limit_response_size'] ) )
996                         $block_size = min( $block_size, $r['limit_response_size'] );
997
998                 // If streaming to a file setup the file handle.
999                 if ( $r['stream'] ) {
1000                         if ( ! WP_DEBUG )
1001                                 $stream_handle = @fopen( $r['filename'], 'w+' );
1002                         else
1003                                 $stream_handle = fopen( $r['filename'], 'w+' );
1004                         if ( ! $stream_handle )
1005                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
1006
1007                         $bytes_written = 0;
1008                         while ( ! feof($handle) && $keep_reading ) {
1009                                 $block = fread( $handle, $block_size );
1010                                 if ( ! $bodyStarted ) {
1011                                         $strResponse .= $block;
1012                                         if ( strpos( $strResponse, "\r\n\r\n" ) ) {
1013                                                 $process = WP_Http::processResponse( $strResponse );
1014                                                 $bodyStarted = true;
1015                                                 $block = $process['body'];
1016                                                 unset( $strResponse );
1017                                                 $process['body'] = '';
1018                                         }
1019                                 }
1020
1021                                 $this_block_size = strlen( $block );
1022
1023                                 if ( isset( $r['limit_response_size'] ) && ( $bytes_written + $this_block_size ) > $r['limit_response_size'] )
1024                                         $block = substr( $block, 0, ( $r['limit_response_size'] - $bytes_written ) );
1025
1026                                 $bytes_written_to_file = fwrite( $stream_handle, $block );
1027
1028                                 if ( $bytes_written_to_file != $this_block_size ) {
1029                                         fclose( $handle );
1030                                         fclose( $stream_handle );
1031                                         return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
1032                                 }
1033
1034                                 $bytes_written += $bytes_written_to_file;
1035
1036                                 $keep_reading = !isset( $r['limit_response_size'] ) || $bytes_written < $r['limit_response_size'];
1037                         }
1038
1039                         fclose( $stream_handle );
1040
1041                 } else {
1042                         $header_length = 0;
1043                         while ( ! feof( $handle ) && $keep_reading ) {
1044                                 $block = fread( $handle, $block_size );
1045                                 $strResponse .= $block;
1046                                 if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
1047                                         $header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
1048                                         $bodyStarted = true;
1049                                 }
1050                                 $keep_reading = ( ! $bodyStarted || !isset( $r['limit_response_size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit_response_size'] ) );
1051                         }
1052
1053                         $process = WP_Http::processResponse( $strResponse );
1054                         unset( $strResponse );
1055
1056                 }
1057
1058                 fclose( $handle );
1059
1060                 $arrHeaders = WP_Http::processHeaders( $process['headers'], $url );
1061
1062                 $response = array(
1063                         'headers' => $arrHeaders['headers'],
1064                         // Not yet processed.
1065                         'body' => null,
1066                         'response' => $arrHeaders['response'],
1067                         'cookies' => $arrHeaders['cookies'],
1068                         'filename' => $r['filename']
1069                 );
1070
1071                 // Handle redirects.
1072                 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
1073                         return $redirect_response;
1074
1075                 // If the body was chunk encoded, then decode it.
1076                 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
1077                         $process['body'] = WP_Http::chunkTransferDecode($process['body']);
1078
1079                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
1080                         $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
1081
1082                 if ( isset( $r['limit_response_size'] ) && strlen( $process['body'] ) > $r['limit_response_size'] )
1083                         $process['body'] = substr( $process['body'], 0, $r['limit_response_size'] );
1084
1085                 $response['body'] = $process['body'];
1086
1087                 return $response;
1088         }
1089
1090         /**
1091          * Verifies the received SSL certificate against it's Common Names and subjectAltName fields
1092          *
1093          * PHP's SSL verifications only verify that it's a valid Certificate, it doesn't verify if
1094          * the certificate is valid for the hostname which was requested.
1095          * This function verifies the requested hostname against certificate's subjectAltName field,
1096          * if that is empty, or contains no DNS entries, a fallback to the Common Name field is used.
1097          *
1098          * IP Address support is included if the request is being made to an IP address.
1099          *
1100          * @since 3.7.0
1101          * @static
1102          *
1103          * @param stream $stream The PHP Stream which the SSL request is being made over
1104          * @param string $host The hostname being requested
1105          * @return bool If the cerficiate presented in $stream is valid for $host
1106          */
1107         public static function verify_ssl_certificate( $stream, $host ) {
1108                 $context_options = stream_context_get_options( $stream );
1109
1110                 if ( empty( $context_options['ssl']['peer_certificate'] ) )
1111                         return false;
1112
1113                 $cert = openssl_x509_parse( $context_options['ssl']['peer_certificate'] );
1114                 if ( ! $cert )
1115                         return false;
1116
1117                 /*
1118                  * If the request is being made to an IP address, we'll validate against IP fields
1119                  * in the cert (if they exist)
1120                  */
1121                 $host_type = ( WP_HTTP::is_ip_address( $host ) ? 'ip' : 'dns' );
1122
1123                 $certificate_hostnames = array();
1124                 if ( ! empty( $cert['extensions']['subjectAltName'] ) ) {
1125                         $match_against = preg_split( '/,\s*/', $cert['extensions']['subjectAltName'] );
1126                         foreach ( $match_against as $match ) {
1127                                 list( $match_type, $match_host ) = explode( ':', $match );
1128                                 if ( $host_type == strtolower( trim( $match_type ) ) ) // IP: or DNS:
1129                                         $certificate_hostnames[] = strtolower( trim( $match_host ) );
1130                         }
1131                 } elseif ( !empty( $cert['subject']['CN'] ) ) {
1132                         // Only use the CN when the certificate includes no subjectAltName extension.
1133                         $certificate_hostnames[] = strtolower( $cert['subject']['CN'] );
1134                 }
1135
1136                 // Exact hostname/IP matches.
1137                 if ( in_array( strtolower( $host ), $certificate_hostnames ) )
1138                         return true;
1139
1140                 // IP's can't be wildcards, Stop processing.
1141                 if ( 'ip' == $host_type )
1142                         return false;
1143
1144                 // Test to see if the domain is at least 2 deep for wildcard support.
1145                 if ( substr_count( $host, '.' ) < 2 )
1146                         return false;
1147
1148                 // Wildcard subdomains certs (*.example.com) are valid for a.example.com but not a.b.example.com.
1149                 $wildcard_host = preg_replace( '/^[^.]+\./', '*.', $host );
1150
1151                 return in_array( strtolower( $wildcard_host ), $certificate_hostnames );
1152         }
1153
1154         /**
1155          * Whether this class can be used for retrieving a URL.
1156          *
1157          * @static
1158          * @access public
1159          * @since 2.7.0
1160          * @since 3.7.0 Combined with the fsockopen transport and switched to stream_socket_client().
1161          *
1162          * @return boolean False means this class can not be used, true means it can.
1163          */
1164         public static function test( $args = array() ) {
1165                 if ( ! function_exists( 'stream_socket_client' ) )
1166                         return false;
1167
1168                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
1169
1170                 if ( $is_ssl ) {
1171                         if ( ! extension_loaded( 'openssl' ) )
1172                                 return false;
1173                         if ( ! function_exists( 'openssl_x509_parse' ) )
1174                                 return false;
1175                 }
1176
1177                 /**
1178                  * Filter whether streams can be used as a transport for retrieving a URL.
1179                  *
1180                  * @since 2.7.0
1181                  *
1182                  * @param bool  $use_class Whether the class can be used. Default true.
1183                  * @param array $args      Request arguments.
1184                  */
1185                 return apply_filters( 'use_streams_transport', true, $args );
1186         }
1187 }
1188
1189 /**
1190  * Deprecated HTTP Transport method which used fsockopen.
1191  *
1192  * This class is not used, and is included for backwards compatibility only.
1193  * All code should make use of WP_HTTP directly through it's API.
1194  *
1195  * @see WP_HTTP::request
1196  *
1197  * @since 2.7.0
1198  * @deprecated 3.7.0 Please use WP_HTTP::request() directly
1199  */
1200 class WP_HTTP_Fsockopen extends WP_HTTP_Streams {
1201         // For backwards compatibility for users who are using the class directly.
1202 }
1203
1204 /**
1205  * HTTP request method uses Curl extension to retrieve the url.
1206  *
1207  * Requires the Curl extension to be installed.
1208  *
1209  * @package WordPress
1210  * @subpackage HTTP
1211  * @since 2.7.0
1212  */
1213 class WP_Http_Curl {
1214
1215         /**
1216          * Temporary header storage for during requests.
1217          *
1218          * @since 3.2.0
1219          * @access private
1220          * @var string
1221          */
1222         private $headers = '';
1223
1224         /**
1225          * Temporary body storage for during requests.
1226          *
1227          * @since 3.6.0
1228          * @access private
1229          * @var string
1230          */
1231         private $body = '';
1232
1233         /**
1234          * The maximum amount of data to receive from the remote server.
1235          *
1236          * @since 3.6.0
1237          * @access private
1238          * @var int
1239          */
1240         private $max_body_length = false;
1241
1242         /**
1243          * The file resource used for streaming to file.
1244          *
1245          * @since 3.6.0
1246          * @access private
1247          * @var resource
1248          */
1249         private $stream_handle = false;
1250
1251         /**
1252          * Send a HTTP request to a URI using cURL extension.
1253          *
1254          * @access public
1255          * @since 2.7.0
1256          *
1257          * @param string $url The request URL.
1258          * @param string|array $args Optional. Override the defaults.
1259          * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
1260          */
1261         public function request($url, $args = array()) {
1262                 $defaults = array(
1263                         'method' => 'GET', 'timeout' => 5,
1264                         'redirection' => 5, 'httpversion' => '1.0',
1265                         'blocking' => true,
1266                         'headers' => array(), 'body' => null, 'cookies' => array()
1267                 );
1268
1269                 $r = wp_parse_args( $args, $defaults );
1270
1271                 if ( isset($r['headers']['User-Agent']) ) {
1272                         $r['user-agent'] = $r['headers']['User-Agent'];
1273                         unset($r['headers']['User-Agent']);
1274                 } else if ( isset($r['headers']['user-agent']) ) {
1275                         $r['user-agent'] = $r['headers']['user-agent'];
1276                         unset($r['headers']['user-agent']);
1277                 }
1278
1279                 // Construct Cookie: header if any cookies are set.
1280                 WP_Http::buildCookieHeader( $r );
1281
1282                 $handle = curl_init();
1283
1284                 // cURL offers really easy proxy support.
1285                 $proxy = new WP_HTTP_Proxy();
1286
1287                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1288
1289                         curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1290                         curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
1291                         curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
1292
1293                         if ( $proxy->use_authentication() ) {
1294                                 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1295                                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1296                         }
1297                 }
1298
1299                 $is_local = isset($r['local']) && $r['local'];
1300                 $ssl_verify = isset($r['sslverify']) && $r['sslverify'];
1301                 if ( $is_local ) {
1302                         /** This filter is documented in wp-includes/class-http.php */
1303                         $ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
1304                 } elseif ( ! $is_local ) {
1305                         /** This filter is documented in wp-includes/class-http.php */
1306                         $ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
1307                 }
1308
1309                 /*
1310                  * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since.
1311                  * a value of 0 will allow an unlimited timeout.
1312                  */
1313                 $timeout = (int) ceil( $r['timeout'] );
1314                 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
1315                 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
1316
1317                 curl_setopt( $handle, CURLOPT_URL, $url);
1318                 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
1319                 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
1320                 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
1321                 curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] );
1322                 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
1323
1324                 /*
1325                  * The option doesn't work with safe mode or when open_basedir is set, and there's
1326                  * a bug #17490 with redirected POST requests, so handle redirections outside Curl.
1327                  */
1328                 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
1329                 if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
1330                         curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
1331
1332                 switch ( $r['method'] ) {
1333                         case 'HEAD':
1334                                 curl_setopt( $handle, CURLOPT_NOBODY, true );
1335                                 break;
1336                         case 'POST':
1337                                 curl_setopt( $handle, CURLOPT_POST, true );
1338                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1339                                 break;
1340                         case 'PUT':
1341                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
1342                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1343                                 break;
1344                         default:
1345                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
1346                                 if ( ! is_null( $r['body'] ) )
1347                                         curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1348                                 break;
1349                 }
1350
1351                 if ( true === $r['blocking'] ) {
1352                         curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
1353                         curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
1354                 }
1355
1356                 curl_setopt( $handle, CURLOPT_HEADER, false );
1357
1358                 if ( isset( $r['limit_response_size'] ) )
1359                         $this->max_body_length = intval( $r['limit_response_size'] );
1360                 else
1361                         $this->max_body_length = false;
1362
1363                 // If streaming to a file open a file handle, and setup our curl streaming handler.
1364                 if ( $r['stream'] ) {
1365                         if ( ! WP_DEBUG )
1366                                 $this->stream_handle = @fopen( $r['filename'], 'w+' );
1367                         else
1368                                 $this->stream_handle = fopen( $r['filename'], 'w+' );
1369                         if ( ! $this->stream_handle )
1370                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
1371                 } else {
1372                         $this->stream_handle = false;
1373                 }
1374
1375                 if ( !empty( $r['headers'] ) ) {
1376                         // cURL expects full header strings in each element.
1377                         $headers = array();
1378                         foreach ( $r['headers'] as $name => $value ) {
1379                                 $headers[] = "{$name}: $value";
1380                         }
1381                         curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
1382                 }
1383
1384                 if ( $r['httpversion'] == '1.0' )
1385                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
1386                 else
1387                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
1388
1389                 /**
1390                  * Fires before the cURL request is executed.
1391                  *
1392                  * Cookies are not currently handled by the HTTP API. This action allows
1393                  * plugins to handle cookies themselves.
1394                  *
1395                  * @since 2.8.0
1396                  *
1397                  * @param resource &$handle The cURL handle returned by curl_init().
1398                  * @param array    $r       The HTTP request arguments.
1399                  * @param string   $url     The request URL.
1400                  */
1401                 do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) );
1402
1403                 // We don't need to return the body, so don't. Just execute request and return.
1404                 if ( ! $r['blocking'] ) {
1405                         curl_exec( $handle );
1406
1407                         if ( $curl_error = curl_error( $handle ) ) {
1408                                 curl_close( $handle );
1409                                 return new WP_Error( 'http_request_failed', $curl_error );
1410                         }
1411                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
1412                                 curl_close( $handle );
1413                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1414                         }
1415
1416                         curl_close( $handle );
1417                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
1418                 }
1419
1420                 curl_exec( $handle );
1421                 $theHeaders = WP_Http::processHeaders( $this->headers, $url );
1422                 $theBody = $this->body;
1423
1424                 $this->headers = '';
1425                 $this->body = '';
1426
1427                 $curl_error = curl_errno( $handle );
1428
1429                 // If an error occurred, or, no response.
1430                 if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) {
1431                         if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error &&  $r['stream'] ) {
1432                                 fclose( $this->stream_handle );
1433                                 return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
1434                         }
1435                         if ( $curl_error = curl_error( $handle ) ) {
1436                                 curl_close( $handle );
1437                                 return new WP_Error( 'http_request_failed', $curl_error );
1438                         }
1439                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
1440                                 curl_close( $handle );
1441                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1442                         }
1443                 }
1444
1445                 $response = array();
1446                 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
1447                 $response['message'] = get_status_header_desc($response['code']);
1448
1449                 curl_close( $handle );
1450
1451                 if ( $r['stream'] )
1452                         fclose( $this->stream_handle );
1453
1454                 $response = array(
1455                         'headers' => $theHeaders['headers'],
1456                         'body' => null,
1457                         'response' => $response,
1458                         'cookies' => $theHeaders['cookies'],
1459                         'filename' => $r['filename']
1460                 );
1461
1462                 // Handle redirects.
1463                 if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
1464                         return $redirect_response;
1465
1466                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
1467                         $theBody = WP_Http_Encoding::decompress( $theBody );
1468
1469                 $response['body'] = $theBody;
1470
1471                 return $response;
1472         }
1473
1474         /**
1475          * Grab the headers of the cURL request
1476          *
1477          * Each header is sent individually to this callback, so we append to the $header property for temporary storage
1478          *
1479          * @since 3.2.0
1480          * @access private
1481          * @return int
1482          */
1483         private function stream_headers( $handle, $headers ) {
1484                 $this->headers .= $headers;
1485                 return strlen( $headers );
1486         }
1487
1488         /**
1489          * Grab the body of the cURL request
1490          *
1491          * The contents of the document are passed in chunks, so we append to the $body property for temporary storage.
1492          * Returning a length shorter than the length of $data passed in will cause cURL to abort the request as "completed"
1493          *
1494          * @since 3.6.0
1495          * @access private
1496          * @return int
1497          */
1498         private function stream_body( $handle, $data ) {
1499                 $data_length = strlen( $data );
1500
1501                 if ( $this->max_body_length && ( strlen( $this->body ) + $data_length ) > $this->max_body_length )
1502                         $data = substr( $data, 0, ( $this->max_body_length - $data_length ) );
1503
1504                 if ( $this->stream_handle ) {
1505                         $bytes_written = fwrite( $this->stream_handle, $data );
1506                 } else {
1507                         $this->body .= $data;
1508                         $bytes_written = $data_length;
1509                 }
1510
1511                 // Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
1512                 return $bytes_written;
1513         }
1514
1515         /**
1516          * Whether this class can be used for retrieving an URL.
1517          *
1518          * @static
1519          * @since 2.7.0
1520          *
1521          * @return boolean False means this class can not be used, true means it can.
1522          */
1523         public static function test( $args = array() ) {
1524                 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
1525                         return false;
1526
1527                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
1528
1529                 if ( $is_ssl ) {
1530                         $curl_version = curl_version();
1531                         // Check whether this cURL version support SSL requests.
1532                         if ( ! (CURL_VERSION_SSL & $curl_version['features']) )
1533                                 return false;
1534                 }
1535
1536                 /**
1537                  * Filter whether cURL can be used as a transport for retrieving a URL.
1538                  *
1539                  * @since 2.7.0
1540                  *
1541                  * @param bool  $use_class Whether the class can be used. Default true.
1542                  * @param array $args      An array of request arguments.
1543                  */
1544                 return apply_filters( 'use_curl_transport', true, $args );
1545         }
1546 }
1547
1548 /**
1549  * Adds Proxy support to the WordPress HTTP API.
1550  *
1551  * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to
1552  * enable proxy support. There are also a few filters that plugins can hook into for some of the
1553  * constants.
1554  *
1555  * Please note that only BASIC authentication is supported by most transports.
1556  * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
1557  *
1558  * The constants are as follows:
1559  * <ol>
1560  * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li>
1561  * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li>
1562  * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li>
1563  * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li>
1564  * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy.
1565  * You do not need to have localhost and the blog host in this list, because they will not be passed
1566  * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org</li>
1567  * </ol>
1568  *
1569  * An example can be as seen below.
1570  * <code>
1571  * define('WP_PROXY_HOST', '192.168.84.101');
1572  * define('WP_PROXY_PORT', '8080');
1573  * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
1574  * </code>
1575  *
1576  * @link http://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress.
1577  * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS
1578  * @since 2.8.0
1579  */
1580 class WP_HTTP_Proxy {
1581
1582         /**
1583          * Whether proxy connection should be used.
1584          *
1585          * @since 2.8.0
1586          *
1587          * @use WP_PROXY_HOST
1588          * @use WP_PROXY_PORT
1589          *
1590          * @return bool
1591          */
1592         public function is_enabled() {
1593                 return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT');
1594         }
1595
1596         /**
1597          * Whether authentication should be used.
1598          *
1599          * @since 2.8.0
1600          *
1601          * @use WP_PROXY_USERNAME
1602          * @use WP_PROXY_PASSWORD
1603          *
1604          * @return bool
1605          */
1606         public function use_authentication() {
1607                 return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD');
1608         }
1609
1610         /**
1611          * Retrieve the host for the proxy server.
1612          *
1613          * @since 2.8.0
1614          *
1615          * @return string
1616          */
1617         public function host() {
1618                 if ( defined('WP_PROXY_HOST') )
1619                         return WP_PROXY_HOST;
1620
1621                 return '';
1622         }
1623
1624         /**
1625          * Retrieve the port for the proxy server.
1626          *
1627          * @since 2.8.0
1628          *
1629          * @return string
1630          */
1631         public function port() {
1632                 if ( defined('WP_PROXY_PORT') )
1633                         return WP_PROXY_PORT;
1634
1635                 return '';
1636         }
1637
1638         /**
1639          * Retrieve the username for proxy authentication.
1640          *
1641          * @since 2.8.0
1642          *
1643          * @return string
1644          */
1645         public function username() {
1646                 if ( defined('WP_PROXY_USERNAME') )
1647                         return WP_PROXY_USERNAME;
1648
1649                 return '';
1650         }
1651
1652         /**
1653          * Retrieve the password for proxy authentication.
1654          *
1655          * @since 2.8.0
1656          *
1657          * @return string
1658          */
1659         public function password() {
1660                 if ( defined('WP_PROXY_PASSWORD') )
1661                         return WP_PROXY_PASSWORD;
1662
1663                 return '';
1664         }
1665
1666         /**
1667          * Retrieve authentication string for proxy authentication.
1668          *
1669          * @since 2.8.0
1670          *
1671          * @return string
1672          */
1673         public function authentication() {
1674                 return $this->username() . ':' . $this->password();
1675         }
1676
1677         /**
1678          * Retrieve header string for proxy authentication.
1679          *
1680          * @since 2.8.0
1681          *
1682          * @return string
1683          */
1684         public function authentication_header() {
1685                 return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() );
1686         }
1687
1688         /**
1689          * Whether URL should be sent through the proxy server.
1690          *
1691          * We want to keep localhost and the blog URL from being sent through the proxy server, because
1692          * some proxies can not handle this. We also have the constant available for defining other
1693          * hosts that won't be sent through the proxy.
1694          *
1695          * @uses WP_PROXY_BYPASS_HOSTS
1696          * @since 2.8.0
1697          *
1698          * @param string $uri URI to check.
1699          * @return bool True, to send through the proxy and false if, the proxy should not be used.
1700          */
1701         public function send_through_proxy( $uri ) {
1702                 /*
1703                  * parse_url() only handles http, https type URLs, and will emit E_WARNING on failure.
1704                  * This will be displayed on blogs, which is not reasonable.
1705                  */
1706                 $check = @parse_url($uri);
1707
1708                 // Malformed URL, can not process, but this could mean ssl, so let through anyway.
1709                 if ( $check === false )
1710                         return true;
1711
1712                 $home = parse_url( get_option('siteurl') );
1713
1714                 /**
1715                  * Filter whether to preempt sending the request through the proxy server.
1716                  *
1717                  * Returning false will bypass the proxy; returning true will send
1718                  * the request through the proxy. Returning null bypasses the filter.
1719                  *
1720                  * @since 3.5.0
1721                  *
1722                  * @param null   $override Whether to override the request result. Default null.
1723                  * @param string $uri      URL to check.
1724                  * @param array  $check    Associative array result of parsing the URI.
1725                  * @param array  $home     Associative array result of parsing the site URL.
1726                  */
1727                 $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home );
1728                 if ( ! is_null( $result ) )
1729                         return $result;
1730
1731                 if ( 'localhost' == $check['host'] || ( isset( $home['host'] ) && $home['host'] == $check['host'] ) )
1732                         return false;
1733
1734                 if ( !defined('WP_PROXY_BYPASS_HOSTS') )
1735                         return true;
1736
1737                 static $bypass_hosts;
1738                 static $wildcard_regex = false;
1739                 if ( null == $bypass_hosts ) {
1740                         $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS);
1741
1742                         if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) {
1743                                 $wildcard_regex = array();
1744                                 foreach ( $bypass_hosts as $host )
1745                                         $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
1746                                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
1747                         }
1748                 }
1749
1750                 if ( !empty($wildcard_regex) )
1751                         return !preg_match($wildcard_regex, $check['host']);
1752                 else
1753                         return !in_array( $check['host'], $bypass_hosts );
1754         }
1755 }
1756 /**
1757  * Internal representation of a single cookie.
1758  *
1759  * Returned cookies are represented using this class, and when cookies are set, if they are not
1760  * already a WP_Http_Cookie() object, then they are turned into one.
1761  *
1762  * @todo The WordPress convention is to use underscores instead of camelCase for function and method
1763  * names. Need to switch to use underscores instead for the methods.
1764  *
1765  * @package WordPress
1766  * @subpackage HTTP
1767  * @since 2.8.0
1768  */
1769 class WP_Http_Cookie {
1770
1771         /**
1772          * Cookie name.
1773          *
1774          * @since 2.8.0
1775          * @var string
1776          */
1777         public $name;
1778
1779         /**
1780          * Cookie value.
1781          *
1782          * @since 2.8.0
1783          * @var string
1784          */
1785         public $value;
1786
1787         /**
1788          * When the cookie expires.
1789          *
1790          * @since 2.8.0
1791          * @var string
1792          */
1793         public $expires;
1794
1795         /**
1796          * Cookie URL path.
1797          *
1798          * @since 2.8.0
1799          * @var string
1800          */
1801         public $path;
1802
1803         /**
1804          * Cookie Domain.
1805          *
1806          * @since 2.8.0
1807          * @var string
1808          */
1809         public $domain;
1810
1811         /**
1812          * Sets up this cookie object.
1813          *
1814          * The parameter $data should be either an associative array containing the indices names below
1815          * or a header string detailing it.
1816          *
1817          * @since 2.8.0
1818          * @access public
1819          *
1820          * @param string|array $data {
1821          *     Raw cookie data as header string or data array.
1822          *
1823          *     @type string     $name    Cookie name.
1824          *     @type mixed      $value   Value. Should NOT already be urlencoded.
1825          *     @type string|int $expires Optional. Unix timestamp or formatted date. Default null.
1826          *     @type string     $path    Optional. Path. Default '/'.
1827          *     @type string     $domain  Optional. Domain. Default host of parsed $requested_url.
1828          *     @type int        $port    Optional. Port. Default null.
1829          * }
1830          * @param string       $requested_url The URL which the cookie was set on, used for default $domain
1831          *                                    and $port values.
1832          */
1833         public function __construct( $data, $requested_url = '' ) {
1834                 if ( $requested_url )
1835                         $arrURL = @parse_url( $requested_url );
1836                 if ( isset( $arrURL['host'] ) )
1837                         $this->domain = $arrURL['host'];
1838                 $this->path = isset( $arrURL['path'] ) ? $arrURL['path'] : '/';
1839                 if (  '/' != substr( $this->path, -1 ) )
1840                         $this->path = dirname( $this->path ) . '/';
1841
1842                 if ( is_string( $data ) ) {
1843                         // Assume it's a header string direct from a previous request.
1844                         $pairs = explode( ';', $data );
1845
1846                         // Special handling for first pair; name=value. Also be careful of "=" in value.
1847                         $name  = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) );
1848                         $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 );
1849                         $this->name  = $name;
1850                         $this->value = urldecode( $value );
1851
1852                         // Removes name=value from items.
1853                         array_shift( $pairs );
1854
1855                         // Set everything else as a property.
1856                         foreach ( $pairs as $pair ) {
1857                                 $pair = rtrim($pair);
1858
1859                                 // Handle the cookie ending in ; which results in a empty final pair.
1860                                 if ( empty($pair) )
1861                                         continue;
1862
1863                                 list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' );
1864                                 $key = strtolower( trim( $key ) );
1865                                 if ( 'expires' == $key )
1866                                         $val = strtotime( $val );
1867                                 $this->$key = $val;
1868                         }
1869                 } else {
1870                         if ( !isset( $data['name'] ) )
1871                                 return false;
1872
1873                         // Set properties based directly on parameters.
1874                         foreach ( array( 'name', 'value', 'path', 'domain', 'port' ) as $field ) {
1875                                 if ( isset( $data[ $field ] ) )
1876                                         $this->$field = $data[ $field ];
1877                         }
1878
1879                         if ( isset( $data['expires'] ) )
1880                                 $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] );
1881                         else
1882                                 $this->expires = null;
1883                 }
1884         }
1885
1886         /**
1887          * Confirms that it's OK to send this cookie to the URL checked against.
1888          *
1889          * Decision is based on RFC 2109/2965, so look there for details on validity.
1890          *
1891          * @access public
1892          * @since 2.8.0
1893          *
1894          * @param string $url URL you intend to send this cookie to
1895          * @return boolean true if allowed, false otherwise.
1896          */
1897         public function test( $url ) {
1898                 if ( is_null( $this->name ) )
1899                         return false;
1900
1901                 // Expires - if expired then nothing else matters.
1902                 if ( isset( $this->expires ) && time() > $this->expires )
1903                         return false;
1904
1905                 // Get details on the URL we're thinking about sending to.
1906                 $url = parse_url( $url );
1907                 $url['port'] = isset( $url['port'] ) ? $url['port'] : ( 'https' == $url['scheme'] ? 443 : 80 );
1908                 $url['path'] = isset( $url['path'] ) ? $url['path'] : '/';
1909
1910                 // Values to use for comparison against the URL.
1911                 $path   = isset( $this->path )   ? $this->path   : '/';
1912                 $port   = isset( $this->port )   ? $this->port   : null;
1913                 $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] );
1914                 if ( false === stripos( $domain, '.' ) )
1915                         $domain .= '.local';
1916
1917                 // Host - very basic check that the request URL ends with the domain restriction (minus leading dot).
1918                 $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain;
1919                 if ( substr( $url['host'], -strlen( $domain ) ) != $domain )
1920                         return false;
1921
1922                 // Port - supports "port-lists" in the format: "80,8000,8080".
1923                 if ( !empty( $port ) && !in_array( $url['port'], explode( ',', $port) ) )
1924                         return false;
1925
1926                 // Path - request path must start with path restriction.
1927                 if ( substr( $url['path'], 0, strlen( $path ) ) != $path )
1928                         return false;
1929
1930                 return true;
1931         }
1932
1933         /**
1934          * Convert cookie name and value back to header string.
1935          *
1936          * @access public
1937          * @since 2.8.0
1938          *
1939          * @return string Header encoded cookie name and value.
1940          */
1941         public function getHeaderValue() {
1942                 if ( ! isset( $this->name ) || ! isset( $this->value ) )
1943                         return '';
1944
1945                 /**
1946                  * Filter the header-encoded cookie value.
1947                  *
1948                  * @since 3.4.0
1949                  *
1950                  * @param string $value The cookie value.
1951                  * @param string $name  The cookie name.
1952                  */
1953                 return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name );
1954         }
1955
1956         /**
1957          * Retrieve cookie header for usage in the rest of the WordPress HTTP API.
1958          *
1959          * @access public
1960          * @since 2.8.0
1961          *
1962          * @return string
1963          */
1964         public function getFullHeader() {
1965                 return 'Cookie: ' . $this->getHeaderValue();
1966         }
1967 }
1968
1969 /**
1970  * Implementation for deflate and gzip transfer encodings.
1971  *
1972  * Includes RFC 1950, RFC 1951, and RFC 1952.
1973  *
1974  * @since 2.8.0
1975  * @package WordPress
1976  * @subpackage HTTP
1977  */
1978 class WP_Http_Encoding {
1979
1980         /**
1981          * Compress raw string using the deflate format.
1982          *
1983          * Supports the RFC 1951 standard.
1984          *
1985          * @since 2.8.0
1986          *
1987          * @param string $raw String to compress.
1988          * @param int $level Optional, default is 9. Compression level, 9 is highest.
1989          * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports.
1990          * @return string|bool False on failure.
1991          */
1992         public static function compress( $raw, $level = 9, $supports = null ) {
1993                 return gzdeflate( $raw, $level );
1994         }
1995
1996         /**
1997          * Decompression of deflated string.
1998          *
1999          * Will attempt to decompress using the RFC 1950 standard, and if that fails
2000          * then the RFC 1951 standard deflate will be attempted. Finally, the RFC
2001          * 1952 standard gzip decode will be attempted. If all fail, then the
2002          * original compressed string will be returned.
2003          *
2004          * @since 2.8.0
2005          *
2006          * @param string $compressed String to decompress.
2007          * @param int $length The optional length of the compressed data.
2008          * @return string|bool False on failure.
2009          */
2010         public static function decompress( $compressed, $length = null ) {
2011
2012                 if ( empty($compressed) )
2013                         return $compressed;
2014
2015                 if ( false !== ( $decompressed = @gzinflate( $compressed ) ) )
2016                         return $decompressed;
2017
2018                 if ( false !== ( $decompressed = WP_Http_Encoding::compatible_gzinflate( $compressed ) ) )
2019                         return $decompressed;
2020
2021                 if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) )
2022                         return $decompressed;
2023
2024                 if ( function_exists('gzdecode') ) {
2025                         $decompressed = @gzdecode( $compressed );
2026
2027                         if ( false !== $decompressed )
2028                                 return $decompressed;
2029                 }
2030
2031                 return $compressed;
2032         }
2033
2034         /**
2035          * Decompression of deflated string while staying compatible with the majority of servers.
2036          *
2037          * Certain Servers will return deflated data with headers which PHP's gzinflate()
2038          * function cannot handle out of the box. The following function has been created from
2039          * various snippets on the gzinflate() PHP documentation.
2040          *
2041          * Warning: Magic numbers within. Due to the potential different formats that the compressed
2042          * data may be returned in, some "magic offsets" are needed to ensure proper decompression
2043          * takes place. For a simple progmatic way to determine the magic offset in use, see:
2044          * http://core.trac.wordpress.org/ticket/18273
2045          *
2046          * @since 2.8.1
2047          * @link http://core.trac.wordpress.org/ticket/18273
2048          * @link http://au2.php.net/manual/en/function.gzinflate.php#70875
2049          * @link http://au2.php.net/manual/en/function.gzinflate.php#77336
2050          *
2051          * @param string $gzData String to decompress.
2052          * @return string|bool False on failure.
2053          */
2054         public static function compatible_gzinflate($gzData) {
2055
2056                 // Compressed data might contain a full header, if so strip it for gzinflate().
2057                 if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
2058                         $i = 10;
2059                         $flg = ord( substr($gzData, 3, 1) );
2060                         if ( $flg > 0 ) {
2061                                 if ( $flg & 4 ) {
2062                                         list($xlen) = unpack('v', substr($gzData, $i, 2) );
2063                                         $i = $i + 2 + $xlen;
2064                                 }
2065                                 if ( $flg & 8 )
2066                                         $i = strpos($gzData, "\0", $i) + 1;
2067                                 if ( $flg & 16 )
2068                                         $i = strpos($gzData, "\0", $i) + 1;
2069                                 if ( $flg & 2 )
2070                                         $i = $i + 2;
2071                         }
2072                         $decompressed = @gzinflate( substr($gzData, $i, -8) );
2073                         if ( false !== $decompressed )
2074                                 return $decompressed;
2075                 }
2076
2077                 // Compressed data from java.util.zip.Deflater amongst others.
2078                 $decompressed = @gzinflate( substr($gzData, 2) );
2079                 if ( false !== $decompressed )
2080                         return $decompressed;
2081
2082                 return false;
2083         }
2084
2085         /**
2086          * What encoding types to accept and their priority values.
2087          *
2088          * @since 2.8.0
2089          *
2090          * @return string Types of encoding to accept.
2091          */
2092         public static function accept_encoding( $url, $args ) {
2093                 $type = array();
2094                 $compression_enabled = WP_Http_Encoding::is_available();
2095
2096                 if ( ! $args['decompress'] ) // Decompression specifically disabled.
2097                         $compression_enabled = false;
2098                 elseif ( $args['stream'] ) // Disable when streaming to file.
2099                         $compression_enabled = false;
2100                 elseif ( isset( $args['limit_response_size'] ) ) // If only partial content is being requested, we won't be able to decompress it.
2101                         $compression_enabled = false;
2102
2103                 if ( $compression_enabled ) {
2104                         if ( function_exists( 'gzinflate' ) )
2105                                 $type[] = 'deflate;q=1.0';
2106
2107                         if ( function_exists( 'gzuncompress' ) )
2108                                 $type[] = 'compress;q=0.5';
2109
2110                         if ( function_exists( 'gzdecode' ) )
2111                                 $type[] = 'gzip;q=0.5';
2112                 }
2113
2114                 /**
2115                  * Filter the allowed encoding types.
2116                  *
2117                  * @since 3.6.0
2118                  *
2119                  * @param array  $type Encoding types allowed. Accepts 'gzinflate',
2120                  *                     'gzuncompress', 'gzdecode'.
2121                  * @param string $url  URL of the HTTP request.
2122                  * @param array  $args HTTP request arguments.
2123                  */
2124                 $type = apply_filters( 'wp_http_accept_encoding', $type, $url, $args );
2125
2126                 return implode(', ', $type);
2127         }
2128
2129         /**
2130          * What encoding the content used when it was compressed to send in the headers.
2131          *
2132          * @since 2.8.0
2133          *
2134          * @return string Content-Encoding string to send in the header.
2135          */
2136         public static function content_encoding() {
2137                 return 'deflate';
2138         }
2139
2140         /**
2141          * Whether the content be decoded based on the headers.
2142          *
2143          * @since 2.8.0
2144          *
2145          * @param array|string $headers All of the available headers.
2146          * @return bool
2147          */
2148         public static function should_decode($headers) {
2149                 if ( is_array( $headers ) ) {
2150                         if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) )
2151                                 return true;
2152                 } else if ( is_string( $headers ) ) {
2153                         return ( stripos($headers, 'content-encoding:') !== false );
2154                 }
2155
2156                 return false;
2157         }
2158
2159         /**
2160          * Whether decompression and compression are supported by the PHP version.
2161          *
2162          * Each function is tested instead of checking for the zlib extension, to
2163          * ensure that the functions all exist in the PHP version and aren't
2164          * disabled.
2165          *
2166          * @since 2.8.0
2167          *
2168          * @return bool
2169          */
2170         public static function is_available() {
2171                 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );
2172         }
2173 }