3f396a647380df98fbe3bdff2faeef438cf01d6d
[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 called for the functionality of making HTTP requests and replaces Snoopy
19  * functionality. There is no available functionality to add HTTP transport implementations, since
20  * most of the HTTP transports are added and available for use.
21  *
22  * There are no properties, because none are needed and for performance reasons. Some of the
23  * functions are static and while they do have some overhead over functions in PHP4, the purpose is
24  * maintainability. When PHP5 is finally the requirement, it will be easy to add the static keyword
25  * to the code. It is not as easy to convert a function to a method after enough code uses the old
26  * way.
27  *
28  * Debugging includes several actions, which pass different variables for debugging the HTTP API.
29  *
30  * @package WordPress
31  * @subpackage HTTP
32  * @since 2.7.0
33  */
34 class WP_Http {
35
36         /**
37          * Send a HTTP request to a URI.
38          *
39          * The body and headers are part of the arguments. The 'body' argument is for the body and will
40          * accept either a string or an array. The 'headers' argument should be an array, but a string
41          * is acceptable. If the 'body' argument is an array, then it will automatically be escaped
42          * using http_build_query().
43          *
44          * The only URI that are supported in the HTTP Transport implementation are the HTTP and HTTPS
45          * protocols. HTTP and HTTPS are assumed so the server might not know how to handle the send
46          * headers. Other protocols are unsupported and most likely will fail.
47          *
48          * The defaults are 'method', 'timeout', 'redirection', 'httpversion', 'blocking' and
49          * 'user-agent'.
50          *
51          * Accepted 'method' values are 'GET', 'POST', and 'HEAD', some transports technically allow
52          * others, but should not be assumed. The 'timeout' is used to sent how long the connection
53          * should stay open before failing when no response. 'redirection' is used to track how many
54          * redirects were taken and used to sent the amount for other transports, but not all transports
55          * accept setting that value.
56          *
57          * The 'httpversion' option is used to sent the HTTP version and accepted values are '1.0', and
58          * '1.1' and should be a string. Version 1.1 is not supported, because of chunk response. The
59          * 'user-agent' option is the user-agent and is used to replace the default user-agent, which is
60          * 'WordPress/WP_Version', where WP_Version is the value from $wp_version.
61          *
62          * 'blocking' is the default, which is used to tell the transport, whether it should halt PHP
63          * while it performs the request or continue regardless. Actually, that isn't entirely correct.
64          * Blocking mode really just means whether the fread should just pull what it can whenever it
65          * gets bytes or if it should wait until it has enough in the buffer to read or finishes reading
66          * the entire content. It doesn't actually always mean that PHP will continue going after making
67          * the request.
68          *
69          * @access public
70          * @since 2.7.0
71          * @todo Refactor this code. The code in this method extends the scope of its original purpose
72          *              and should be refactored to allow for cleaner abstraction and reduce duplication of the
73          *              code. One suggestion is to create a class specifically for the arguments, however
74          *              preliminary refactoring to this affect has affect more than just the scope of the
75          *              arguments. Something to ponder at least.
76          *
77          * @param string $url URI resource.
78          * @param str|array $args Optional. Override the defaults.
79          * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
80          */
81         function request( $url, $args = array() ) {
82                 global $wp_version;
83
84                 $defaults = array(
85                         'method' => 'GET',
86                         'timeout' => apply_filters( 'http_request_timeout', 5),
87                         'redirection' => apply_filters( 'http_request_redirection_count', 5),
88                         'httpversion' => apply_filters( 'http_request_version', '1.0'),
89                         'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . get_bloginfo( 'url' )  ),
90                         'blocking' => true,
91                         'headers' => array(),
92                         'cookies' => array(),
93                         'body' => null,
94                         'compress' => false,
95                         'decompress' => true,
96                         'sslverify' => true,
97                         'stream' => false,
98                         'filename' => null
99                 );
100
101                 // Pre-parse for the HEAD checks.
102                 $args = wp_parse_args( $args );
103
104                 // By default, Head requests do not cause redirections.
105                 if ( isset($args['method']) && 'HEAD' == $args['method'] )
106                         $defaults['redirection'] = 0;
107
108                 $r = wp_parse_args( $args, $defaults );
109                 $r = apply_filters( 'http_request_args', $r, $url );
110
111                 // Certain classes decrement this, store a copy of the original value for loop purposes.
112                 $r['_redirection'] = $r['redirection'];
113
114                 // Allow plugins to short-circuit the request
115                 $pre = apply_filters( 'pre_http_request', false, $r, $url );
116                 if ( false !== $pre )
117                         return $pre;
118
119                 $arrURL = parse_url( $url );
120
121                 if ( empty( $url ) || empty( $arrURL['scheme'] ) )
122                         return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
123
124                 if ( $this->block_request( $url ) )
125                         return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
126
127                 // Determine if this is a https call and pass that on to the transport functions
128                 // so that we can blacklist the transports that do not support ssl verification
129                 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
130
131                 // Determine if this request is to OUR install of WordPress
132                 $homeURL = parse_url( get_bloginfo( 'url' ) );
133                 $r['local'] = $homeURL['host'] == $arrURL['host'] || 'localhost' == $arrURL['host'];
134                 unset( $homeURL );
135
136                 // If we are streaming to a file but no filename was given drop it in the WP temp dir
137                 // and pick it's name using the basename of the $url
138                 if ( $r['stream']  && empty( $r['filename'] ) )
139                         $r['filename'] = get_temp_dir() . basename( $url );
140
141                 // Force some settings if we are streaming to a file and check for existence and perms of destination directory
142                 if ( $r['stream'] ) {
143                         $r['blocking'] = true;
144                         if ( ! call_user_func( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ? 'win_is_writable' : 'is_writable', dirname( $r['filename'] ) ) )
145                                 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
146                 }
147
148                 if ( is_null( $r['headers'] ) )
149                         $r['headers'] = array();
150
151                 if ( ! is_array( $r['headers'] ) ) {
152                         $processedHeaders = WP_Http::processHeaders( $r['headers'] );
153                         $r['headers'] = $processedHeaders['headers'];
154                 }
155
156                 if ( isset( $r['headers']['User-Agent'] ) ) {
157                         $r['user-agent'] = $r['headers']['User-Agent'];
158                         unset( $r['headers']['User-Agent'] );
159                 }
160
161                 if ( isset( $r['headers']['user-agent'] ) ) {
162                         $r['user-agent'] = $r['headers']['user-agent'];
163                         unset( $r['headers']['user-agent'] );
164                 }
165
166                 // Construct Cookie: header if any cookies are set
167                 WP_Http::buildCookieHeader( $r );
168
169                 if ( WP_Http_Encoding::is_available() )
170                         $r['headers']['Accept-Encoding'] = WP_Http_Encoding::accept_encoding();
171
172                 if ( ( ! is_null( $r['body'] ) && '' != $r['body'] ) || 'POST' == $r['method'] || 'PUT' == $r['method'] ) {
173                         if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
174                                 $r['body'] = http_build_query( $r['body'], null, '&' );
175
176                                 if ( ! isset( $r['headers']['Content-Type'] ) )
177                                         $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' );
178                         }
179
180                         if ( '' === $r['body'] )
181                                 $r['body'] = null;
182
183                         if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
184                                 $r['headers']['Content-Length'] = strlen( $r['body'] );
185                 }
186
187                 return $this->_dispatch_request($url, $r);
188         }
189
190         /**
191          * Tests which transports are capable of supporting the request.
192          *
193          * @since 3.2.0
194          * @access private
195          *
196          * @param array $args Request arguments
197          * @param string $url URL to Request
198          *
199          * @return string|bool Class name for the first transport that claims to support the request. False if no transport claims to support the request.
200          */
201         public function _get_first_available_transport( $args, $url = null ) {
202                 $request_order = array( 'curl', 'streams', 'fsockopen' );
203
204                 // Loop over each transport on each HTTP request looking for one which will serve this request's needs
205                 foreach ( $request_order as $transport ) {
206                         $class = 'WP_HTTP_' . $transport;
207
208                         // Check to see if this transport is a possibility, calls the transport statically
209                         if ( !call_user_func( array( $class, 'test' ), $args, $url ) )
210                                 continue;
211
212                         return $class;
213                 }
214
215                 return false;
216         }
217
218         /**
219          * Dispatches a HTTP request to a supporting transport.
220          *
221          * Tests each transport in order to find a transport which matches the request arguments.
222          * Also caches the transport instance to be used later.
223          *
224          * The order for blocking requests is cURL, Streams, and finally Fsockopen.
225          * The order for non-blocking requests is cURL, Streams and Fsockopen().
226          *
227          * There are currently issues with "localhost" not resolving correctly with DNS. This may cause
228          * an error "failed to open stream: A connection attempt failed because the connected party did
229          * not properly respond after a period of time, or established connection failed because [the]
230          * connected host has failed to respond."
231          *
232          * @since 3.2.0
233          * @access private
234          *
235          * @param string $url URL to Request
236          * @param array $args Request arguments
237          * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
238          */
239         private function _dispatch_request( $url, $args ) {
240                 static $transports = array();
241
242                 $class = $this->_get_first_available_transport( $args, $url );
243                 if ( !$class )
244                         return new WP_Error( 'http_failure', __( 'There are no HTTP transports available which can complete the requested request.' ) );
245
246                 // Transport claims to support request, instantiate it and give it a whirl.
247                 if ( empty( $transports[$class] ) )
248                         $transports[$class] = new $class;
249
250                 $response = $transports[$class]->request( $url, $args );
251
252                 do_action( 'http_api_debug', $response, 'response', $class, $args, $url );
253
254                 if ( is_wp_error( $response ) )
255                         return $response;
256
257                 return apply_filters( 'http_response', $response, $args, $url );
258         }
259
260         /**
261          * Uses the POST HTTP method.
262          *
263          * Used for sending data that is expected to be in the body.
264          *
265          * @access public
266          * @since 2.7.0
267          *
268          * @param string $url URI resource.
269          * @param str|array $args Optional. Override the defaults.
270          * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
271          */
272         function post($url, $args = array()) {
273                 $defaults = array('method' => 'POST');
274                 $r = wp_parse_args( $args, $defaults );
275                 return $this->request($url, $r);
276         }
277
278         /**
279          * Uses the GET HTTP method.
280          *
281          * Used for sending data that is expected to be in the body.
282          *
283          * @access public
284          * @since 2.7.0
285          *
286          * @param string $url URI resource.
287          * @param str|array $args Optional. Override the defaults.
288          * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
289          */
290         function get($url, $args = array()) {
291                 $defaults = array('method' => 'GET');
292                 $r = wp_parse_args( $args, $defaults );
293                 return $this->request($url, $r);
294         }
295
296         /**
297          * Uses the HEAD HTTP method.
298          *
299          * Used for sending data that is expected to be in the body.
300          *
301          * @access public
302          * @since 2.7.0
303          *
304          * @param string $url URI resource.
305          * @param str|array $args Optional. Override the defaults.
306          * @return array|object Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
307          */
308         function head($url, $args = array()) {
309                 $defaults = array('method' => 'HEAD');
310                 $r = wp_parse_args( $args, $defaults );
311                 return $this->request($url, $r);
312         }
313
314         /**
315          * Parses the responses and splits the parts into headers and body.
316          *
317          * @access public
318          * @static
319          * @since 2.7.0
320          *
321          * @param string $strResponse The full response string
322          * @return array Array with 'headers' and 'body' keys.
323          */
324         function processResponse($strResponse) {
325                 $res = explode("\r\n\r\n", $strResponse, 2);
326
327                 return array('headers' => $res[0], 'body' => isset($res[1]) ? $res[1] : '');
328         }
329
330         /**
331          * Transform header string into an array.
332          *
333          * If an array is given then it is assumed to be raw header data with numeric keys with the
334          * headers as the values. No headers must be passed that were already processed.
335          *
336          * @access public
337          * @static
338          * @since 2.7.0
339          *
340          * @param string|array $headers
341          * @return array Processed string headers. If duplicate headers are encountered,
342          *                                      Then a numbered array is returned as the value of that header-key.
343          */
344         public static function processHeaders($headers) {
345                 // split headers, one per array element
346                 if ( is_string($headers) ) {
347                         // tolerate line terminator: CRLF = LF (RFC 2616 19.3)
348                         $headers = str_replace("\r\n", "\n", $headers);
349                         // unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>, <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2)
350                         $headers = preg_replace('/\n[ \t]/', ' ', $headers);
351                         // create the headers array
352                         $headers = explode("\n", $headers);
353                 }
354
355                 $response = array('code' => 0, 'message' => '');
356
357                 // If a redirection has taken place, The headers for each page request may have been passed.
358                 // In this case, determine the final HTTP header and parse from there.
359                 for ( $i = count($headers)-1; $i >= 0; $i-- ) {
360                         if ( !empty($headers[$i]) && false === strpos($headers[$i], ':') ) {
361                                 $headers = array_splice($headers, $i);
362                                 break;
363                         }
364                 }
365
366                 $cookies = array();
367                 $newheaders = array();
368                 foreach ( (array) $headers as $tempheader ) {
369                         if ( empty($tempheader) )
370                                 continue;
371
372                         if ( false === strpos($tempheader, ':') ) {
373                                 $stack = explode(' ', $tempheader, 3);
374                                 $stack[] = '';
375                                 list( , $response['code'], $response['message']) = $stack;
376                                 continue;
377                         }
378
379                         list($key, $value) = explode(':', $tempheader, 2);
380
381                         $key = strtolower( $key );
382                         $value = trim( $value );
383
384                         if ( isset( $newheaders[ $key ] ) ) {
385                                 if ( ! is_array( $newheaders[ $key ] ) )
386                                         $newheaders[$key] = array( $newheaders[ $key ] );
387                                 $newheaders[ $key ][] = $value;
388                         } else {
389                                 $newheaders[ $key ] = $value;
390                         }
391                         if ( 'set-cookie' == $key )
392                                 $cookies[] = new WP_Http_Cookie( $value );
393                 }
394
395                 return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
396         }
397
398         /**
399          * Takes the arguments for a ::request() and checks for the cookie array.
400          *
401          * If it's found, then it's assumed to contain WP_Http_Cookie objects, which are each parsed
402          * into strings and added to the Cookie: header (within the arguments array). Edits the array by
403          * reference.
404          *
405          * @access public
406          * @version 2.8.0
407          * @static
408          *
409          * @param array $r Full array of args passed into ::request()
410          */
411         public static function buildCookieHeader( &$r ) {
412                 if ( ! empty($r['cookies']) ) {
413                         $cookies_header = '';
414                         foreach ( (array) $r['cookies'] as $cookie ) {
415                                 $cookies_header .= $cookie->getHeaderValue() . '; ';
416                         }
417                         $cookies_header = substr( $cookies_header, 0, -2 );
418                         $r['headers']['cookie'] = $cookies_header;
419                 }
420         }
421
422         /**
423          * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
424          *
425          * Based off the HTTP http_encoding_dechunk function. Does not support UTF-8. Does not support
426          * returning footer headers. Shouldn't be too difficult to support it though.
427          *
428          * @link http://tools.ietf.org/html/rfc2616#section-19.4.6 Process for chunked decoding.
429          *
430          * @todo Add support for footer chunked headers.
431          * @access public
432          * @since 2.7.0
433          * @static
434          *
435          * @param string $body Body content
436          * @return string Chunked decoded body on success or raw body on failure.
437          */
438         function chunkTransferDecode($body) {
439                 $body = str_replace(array("\r\n", "\r"), "\n", $body);
440                 // The body is not chunked encoding or is malformed.
441                 if ( ! preg_match( '/^[0-9a-f]+(\s|\n)+/mi', trim($body) ) )
442                         return $body;
443
444                 $parsedBody = '';
445                 //$parsedHeaders = array(); Unsupported
446
447                 while ( true ) {
448                         $hasChunk = (bool) preg_match( '/^([0-9a-f]+)(\s|\n)+/mi', $body, $match );
449
450                         if ( $hasChunk ) {
451                                 if ( empty( $match[1] ) )
452                                         return $body;
453
454                                 $length = hexdec( $match[1] );
455                                 $chunkLength = strlen( $match[0] );
456
457                                 $strBody = substr($body, $chunkLength, $length);
458                                 $parsedBody .= $strBody;
459
460                                 $body = ltrim(str_replace(array($match[0], $strBody), '', $body), "\n");
461
462                                 if ( "0" == trim($body) )
463                                         return $parsedBody; // Ignore footer headers.
464                         } else {
465                                 return $body;
466                         }
467                 }
468         }
469
470         /**
471          * Block requests through the proxy.
472          *
473          * Those who are behind a proxy and want to prevent access to certain hosts may do so. This will
474          * prevent plugins from working and core functionality, if you don't include api.wordpress.org.
475          *
476          * You block external URL requests by defining WP_HTTP_BLOCK_EXTERNAL as true in your wp-config.php
477          * file and this will only allow localhost and your blog to make requests. The constant
478          * WP_ACCESSIBLE_HOSTS will allow additional hosts to go through for requests. The format of the
479          * WP_ACCESSIBLE_HOSTS constant is a comma separated list of hostnames to allow, wildcard domains
480          * are supported, eg *.wordpress.org will allow for all subdomains of wordpress.org to be contacted.
481          *
482          * @since 2.8.0
483          * @link http://core.trac.wordpress.org/ticket/8927 Allow preventing external requests.
484          * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_ACCESSIBLE_HOSTS
485          *
486          * @param string $uri URI of url.
487          * @return bool True to block, false to allow.
488          */
489         function block_request($uri) {
490                 // We don't need to block requests, because nothing is blocked.
491                 if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL )
492                         return false;
493
494                 // parse_url() only handles http, https type URLs, and will emit E_WARNING on failure.
495                 // This will be displayed on blogs, which is not reasonable.
496                 $check = @parse_url($uri);
497
498                 /* Malformed URL, can not process, but this could mean ssl, so let through anyway.
499                  *
500                  * This isn't very security sound. There are instances where a hacker might attempt
501                  * to bypass the proxy and this check. However, the reason for this behavior is that
502                  * WordPress does not do any checking currently for non-proxy requests, so it is keeps with
503                  * the default unsecure nature of the HTTP request.
504                  */
505                 if ( $check === false )
506                         return false;
507
508                 $home = parse_url( get_option('siteurl') );
509
510                 // Don't block requests back to ourselves by default
511                 if ( $check['host'] == 'localhost' || $check['host'] == $home['host'] )
512                         return apply_filters('block_local_requests', false);
513
514                 if ( !defined('WP_ACCESSIBLE_HOSTS') )
515                         return true;
516
517                 static $accessible_hosts;
518                 static $wildcard_regex = false;
519                 if ( null == $accessible_hosts ) {
520                         $accessible_hosts = preg_split('|,\s*|', WP_ACCESSIBLE_HOSTS);
521
522                         if ( false !== strpos(WP_ACCESSIBLE_HOSTS, '*') ) {
523                                 $wildcard_regex = array();
524                                 foreach ( $accessible_hosts as $host )
525                                         $wildcard_regex[] = str_replace('\*', '[\w.]+?', preg_quote($host, '/'));
526                                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
527                         }
528                 }
529
530                 if ( !empty($wildcard_regex) )
531                         return !preg_match($wildcard_regex, $check['host']);
532                 else
533                         return !in_array( $check['host'], $accessible_hosts ); //Inverse logic, If its in the array, then we can't access it.
534
535         }
536
537         static function make_absolute_url( $maybe_relative_path, $url ) {
538                 if ( empty( $url ) )
539                         return $maybe_relative_path;
540
541                 // Check for a scheme
542                 if ( false !== strpos( $maybe_relative_path, '://' ) )
543                         return $maybe_relative_path;
544
545                 if ( ! $url_parts = @parse_url( $url ) )
546                         return $maybe_relative_path;
547
548                 if ( ! $relative_url_parts = @parse_url( $maybe_relative_path ) )
549                         return $maybe_relative_path;
550
551                 $absolute_path = $url_parts['scheme'] . '://' . $url_parts['host'];
552                 if ( isset( $url_parts['port'] ) )
553                         $absolute_path .= ':' . $url_parts['port'];
554
555                 // Start off with the Absolute URL path
556                 $path = ! empty( $url_parts['path'] ) ? $url_parts['path'] : '/';
557
558                 // If the it's a root-relative path, then great
559                 if ( ! empty( $relative_url_parts['path'] ) && '/' == $relative_url_parts['path'][0] ) {
560                         $path = $relative_url_parts['path'];
561
562                 // Else it's a relative path
563                 } elseif ( ! empty( $relative_url_parts['path'] ) ) {
564                         // Strip off any file components from the absolute path
565                         $path = substr( $path, 0, strrpos( $path, '/' ) + 1 );
566
567                         // Build the new path
568                         $path .= $relative_url_parts['path'];
569
570                         // Strip all /path/../ out of the path
571                         while ( strpos( $path, '../' ) > 1 ) {
572                                 $path = preg_replace( '![^/]+/\.\./!', '', $path );
573                         }
574
575                         // Strip any final leading ../ from the path
576                         $path = preg_replace( '!^/(\.\./)+!', '', $path );
577                 }
578
579                 // Add the Query string
580                 if ( ! empty( $relative_url_parts['query'] ) )
581                         $path .= '?' . $relative_url_parts['query'];
582
583                 return $absolute_path . '/' . ltrim( $path, '/' );
584         }
585 }
586
587 /**
588  * HTTP request method uses fsockopen function to retrieve the url.
589  *
590  * This would be the preferred method, but the fsockopen implementation has the most overhead of all
591  * the HTTP transport implementations.
592  *
593  * @package WordPress
594  * @subpackage HTTP
595  * @since 2.7.0
596  */
597 class WP_Http_Fsockopen {
598         /**
599          * Send a HTTP request to a URI using fsockopen().
600          *
601          * Does not support non-blocking mode.
602          *
603          * @see WP_Http::request For default options descriptions.
604          *
605          * @since 2.7
606          * @access public
607          * @param string $url URI resource.
608          * @param str|array $args Optional. Override the defaults.
609          * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
610          */
611         function request($url, $args = array()) {
612                 $defaults = array(
613                         'method' => 'GET', 'timeout' => 5,
614                         'redirection' => 5, 'httpversion' => '1.0',
615                         'blocking' => true,
616                         'headers' => array(), 'body' => null, 'cookies' => array()
617                 );
618
619                 $r = wp_parse_args( $args, $defaults );
620
621                 if ( isset($r['headers']['User-Agent']) ) {
622                         $r['user-agent'] = $r['headers']['User-Agent'];
623                         unset($r['headers']['User-Agent']);
624                 } else if ( isset($r['headers']['user-agent']) ) {
625                         $r['user-agent'] = $r['headers']['user-agent'];
626                         unset($r['headers']['user-agent']);
627                 }
628
629                 // Construct Cookie: header if any cookies are set
630                 WP_Http::buildCookieHeader( $r );
631
632                 $iError = null; // Store error number
633                 $strError = null; // Store error string
634
635                 $arrURL = parse_url($url);
636
637                 $fsockopen_host = $arrURL['host'];
638
639                 $secure_transport = false;
640
641                 if ( ! isset( $arrURL['port'] ) ) {
642                         if ( ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) && extension_loaded('openssl') ) {
643                                 $fsockopen_host = "ssl://$fsockopen_host";
644                                 $arrURL['port'] = 443;
645                                 $secure_transport = true;
646                         } else {
647                                 $arrURL['port'] = 80;
648                         }
649                 }
650
651                 //fsockopen has issues with 'localhost' with IPv6 with certain versions of PHP, It attempts to connect to ::1,
652                 // which fails when the server is not set up for it. For compatibility, always connect to the IPv4 address.
653                 if ( 'localhost' == strtolower($fsockopen_host) )
654                         $fsockopen_host = '127.0.0.1';
655
656                 // There are issues with the HTTPS and SSL protocols that cause errors that can be safely
657                 // ignored and should be ignored.
658                 if ( true === $secure_transport )
659                         $error_reporting = error_reporting(0);
660
661                 $startDelay = time();
662
663                 $proxy = new WP_HTTP_Proxy();
664
665                 if ( !WP_DEBUG ) {
666                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
667                                 $handle = @fsockopen( $proxy->host(), $proxy->port(), $iError, $strError, $r['timeout'] );
668                         else
669                                 $handle = @fsockopen( $fsockopen_host, $arrURL['port'], $iError, $strError, $r['timeout'] );
670                 } else {
671                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
672                                 $handle = fsockopen( $proxy->host(), $proxy->port(), $iError, $strError, $r['timeout'] );
673                         else
674                                 $handle = fsockopen( $fsockopen_host, $arrURL['port'], $iError, $strError, $r['timeout'] );
675                 }
676
677                 $endDelay = time();
678
679                 // If the delay is greater than the timeout then fsockopen shouldn't be used, because it will
680                 // cause a long delay.
681                 $elapseDelay = ($endDelay-$startDelay) > $r['timeout'];
682                 if ( true === $elapseDelay )
683                         add_option( 'disable_fsockopen', $endDelay, null, true );
684
685                 if ( false === $handle )
686                         return new WP_Error('http_request_failed', $iError . ': ' . $strError);
687
688                 $timeout = (int) floor( $r['timeout'] );
689                 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
690                 stream_set_timeout( $handle, $timeout, $utimeout );
691
692                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
693                         $requestPath = $url;
694                 else
695                         $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
696
697                 if ( empty($requestPath) )
698                         $requestPath .= '/';
699
700                 $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
701
702                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
703                         $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
704                 else
705                         $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
706
707                 if ( isset($r['user-agent']) )
708                         $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
709
710                 if ( is_array($r['headers']) ) {
711                         foreach ( (array) $r['headers'] as $header => $headerValue )
712                                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
713                 } else {
714                         $strHeaders .= $r['headers'];
715                 }
716
717                 if ( $proxy->use_authentication() )
718                         $strHeaders .= $proxy->authentication_header() . "\r\n";
719
720                 $strHeaders .= "\r\n";
721
722                 if ( ! is_null($r['body']) )
723                         $strHeaders .= $r['body'];
724
725                 fwrite($handle, $strHeaders);
726
727                 if ( ! $r['blocking'] ) {
728                         fclose($handle);
729                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
730                 }
731
732                 $strResponse = '';
733                 $bodyStarted = false;
734
735                 // If streaming to a file setup the file handle
736                 if ( $r['stream'] ) {
737                         if ( ! WP_DEBUG )
738                                 $stream_handle = @fopen( $r['filename'], 'w+' );
739                         else
740                                 $stream_handle = fopen( $r['filename'], 'w+' );
741                         if ( ! $stream_handle )
742                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
743
744                         while ( ! feof($handle) ) {
745                                 $block = fread( $handle, 4096 );
746                                 if ( $bodyStarted ) {
747                                         fwrite( $stream_handle, $block );
748                                 } else {
749                                         $strResponse .= $block;
750                                         if ( strpos( $strResponse, "\r\n\r\n" ) ) {
751                                                 $process = WP_Http::processResponse( $strResponse );
752                                                 $bodyStarted = true;
753                                                 fwrite( $stream_handle, $process['body'] );
754                                                 unset( $strResponse );
755                                                 $process['body'] = '';
756                                         }
757                                 }
758                         }
759
760                         fclose( $stream_handle );
761
762                 } else {
763                         while ( ! feof($handle) )
764                                 $strResponse .= fread( $handle, 4096 );
765
766                         $process = WP_Http::processResponse( $strResponse );
767                         unset( $strResponse );
768                 }
769
770                 fclose( $handle );
771
772                 if ( true === $secure_transport )
773                         error_reporting($error_reporting);
774
775                 $arrHeaders = WP_Http::processHeaders( $process['headers'] );
776
777                 // If location is found, then assume redirect and redirect to location.
778                 if ( isset($arrHeaders['headers']['location']) && 0 !== $r['_redirection'] ) {
779                         if ( $r['redirection']-- > 0 ) {
780                                 return $this->request( WP_HTTP::make_absolute_url( $arrHeaders['headers']['location'], $url ), $r);
781                         } else {
782                                 return new WP_Error('http_request_failed', __('Too many redirects.'));
783                         }
784                 }
785
786                 // If the body was chunk encoded, then decode it.
787                 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
788                         $process['body'] = WP_Http::chunkTransferDecode($process['body']);
789
790                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
791                         $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
792
793                 return array( 'headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] );
794         }
795
796         /**
797          * Whether this class can be used for retrieving an URL.
798          *
799          * @since 2.7.0
800          * @static
801          * @return boolean False means this class can not be used, true means it can.
802          */
803         public static function test( $args = array() ) {
804                 if ( ! function_exists( 'fsockopen' ) )
805                         return false;
806
807                 if ( false !== ( $option = get_option( 'disable_fsockopen' ) ) && time() - $option < 12 * HOUR_IN_SECONDS )
808                         return false;
809
810                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
811
812                 if ( $is_ssl && ! extension_loaded( 'openssl' ) )
813                         return false;
814
815                 return apply_filters( 'use_fsockopen_transport', true, $args );
816         }
817 }
818
819 /**
820  * HTTP request method uses Streams to retrieve the url.
821  *
822  * Requires PHP 5.0+ and uses fopen with stream context. Requires that 'allow_url_fopen' PHP setting
823  * to be enabled.
824  *
825  * Second preferred method for getting the URL, for PHP 5.
826  *
827  * @package WordPress
828  * @subpackage HTTP
829  * @since 2.7.0
830  */
831 class WP_Http_Streams {
832         /**
833          * Send a HTTP request to a URI using streams with fopen().
834          *
835          * @access public
836          * @since 2.7.0
837          *
838          * @param string $url
839          * @param str|array $args Optional. Override the defaults.
840          * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
841          */
842         function request($url, $args = array()) {
843                 $defaults = array(
844                         'method' => 'GET', 'timeout' => 5,
845                         'redirection' => 5, 'httpversion' => '1.0',
846                         'blocking' => true,
847                         'headers' => array(), 'body' => null, 'cookies' => array()
848                 );
849
850                 $r = wp_parse_args( $args, $defaults );
851
852                 if ( isset($r['headers']['User-Agent']) ) {
853                         $r['user-agent'] = $r['headers']['User-Agent'];
854                         unset($r['headers']['User-Agent']);
855                 } else if ( isset($r['headers']['user-agent']) ) {
856                         $r['user-agent'] = $r['headers']['user-agent'];
857                         unset($r['headers']['user-agent']);
858                 }
859
860                 // Construct Cookie: header if any cookies are set
861                 WP_Http::buildCookieHeader( $r );
862
863                 $arrURL = parse_url($url);
864
865                 if ( false === $arrURL )
866                         return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
867
868                 if ( 'http' != $arrURL['scheme'] && 'https' != $arrURL['scheme'] )
869                         $url = preg_replace('|^' . preg_quote($arrURL['scheme'], '|') . '|', 'http', $url);
870
871                 // Convert Header array to string.
872                 $strHeaders = '';
873                 if ( is_array( $r['headers'] ) )
874                         foreach ( $r['headers'] as $name => $value )
875                                 $strHeaders .= "{$name}: $value\r\n";
876                 else if ( is_string( $r['headers'] ) )
877                         $strHeaders = $r['headers'];
878
879                 $is_local = isset($args['local']) && $args['local'];
880                 $ssl_verify = isset($args['sslverify']) && $args['sslverify'];
881                 if ( $is_local )
882                         $ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify);
883                 elseif ( ! $is_local )
884                         $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
885
886                 $arrContext = array('http' =>
887                         array(
888                                 'method' => strtoupper($r['method']),
889                                 'user_agent' => $r['user-agent'],
890                                 'max_redirects' => $r['redirection'] + 1, // See #11557
891                                 'protocol_version' => (float) $r['httpversion'],
892                                 'header' => $strHeaders,
893                                 'ignore_errors' => true, // Return non-200 requests.
894                                 'timeout' => $r['timeout'],
895                                 'ssl' => array(
896                                                 'verify_peer' => $ssl_verify,
897                                                 'verify_host' => $ssl_verify
898                                 )
899                         )
900                 );
901
902                 $proxy = new WP_HTTP_Proxy();
903
904                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
905                         $arrContext['http']['proxy'] = 'tcp://' . $proxy->host() . ':' . $proxy->port();
906                         $arrContext['http']['request_fulluri'] = true;
907
908                         // We only support Basic authentication so this will only work if that is what your proxy supports.
909                         if ( $proxy->use_authentication() )
910                                 $arrContext['http']['header'] .= $proxy->authentication_header() . "\r\n";
911                 }
912
913                 if ( ! is_null( $r['body'] ) )
914                         $arrContext['http']['content'] = $r['body'];
915
916                 $context = stream_context_create($arrContext);
917
918                 if ( !WP_DEBUG )
919                         $handle = @fopen($url, 'r', false, $context);
920                 else
921                         $handle = fopen($url, 'r', false, $context);
922
923                 if ( ! $handle )
924                         return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
925
926                 $timeout = (int) floor( $r['timeout'] );
927                 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
928                 stream_set_timeout( $handle, $timeout, $utimeout );
929
930                 if ( ! $r['blocking'] ) {
931                         stream_set_blocking($handle, 0);
932                         fclose($handle);
933                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
934                 }
935
936                 if ( $r['stream'] ) {
937                         if ( ! WP_DEBUG )
938                                 $stream_handle = @fopen( $r['filename'], 'w+' );
939                         else
940                                 $stream_handle = fopen( $r['filename'], 'w+' );
941
942                         if ( ! $stream_handle )
943                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
944
945                         stream_copy_to_stream( $handle, $stream_handle );
946
947                         fclose( $stream_handle );
948                         $strResponse = '';
949                 } else {
950                         $strResponse = stream_get_contents( $handle );
951                 }
952
953                 $meta = stream_get_meta_data( $handle );
954
955                 fclose( $handle );
956
957                 $processedHeaders = array();
958                 if ( isset( $meta['wrapper_data']['headers'] ) )
959                         $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']['headers']);
960                 else
961                         $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
962
963                 // Streams does not provide an error code which we can use to see why the request stream stopped.
964                 // We can however test to see if a location header is present and return based on that.
965                 if ( isset($processedHeaders['headers']['location']) && 0 !== $args['_redirection'] )
966                         return new WP_Error('http_request_failed', __('Too many redirects.'));
967
968                 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
969                         $strResponse = WP_Http::chunkTransferDecode($strResponse);
970
971                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders['headers']) )
972                         $strResponse = WP_Http_Encoding::decompress( $strResponse );
973
974                 return array( 'headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies'], 'filename' => $r['filename'] );
975         }
976
977         /**
978          * Whether this class can be used for retrieving an URL.
979          *
980          * @static
981          * @access public
982          * @since 2.7.0
983          *
984          * @return boolean False means this class can not be used, true means it can.
985          */
986         public static function test( $args = array() ) {
987                 if ( ! function_exists( 'fopen' ) )
988                         return false;
989
990                 if ( ! function_exists( 'ini_get' ) || true != ini_get( 'allow_url_fopen' ) )
991                         return false;
992
993                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
994
995                 if ( $is_ssl && ! extension_loaded( 'openssl' ) )
996                         return false;
997
998                 return apply_filters( 'use_streams_transport', true, $args );
999         }
1000 }
1001
1002 /**
1003  * HTTP request method uses Curl extension to retrieve the url.
1004  *
1005  * Requires the Curl extension to be installed.
1006  *
1007  * @package WordPress
1008  * @subpackage HTTP
1009  * @since 2.7
1010  */
1011 class WP_Http_Curl {
1012
1013         /**
1014          * Temporary header storage for use with streaming to a file.
1015          *
1016          * @since 3.2.0
1017          * @access private
1018          * @var string
1019          */
1020         private $headers = '';
1021
1022         /**
1023          * Send a HTTP request to a URI using cURL extension.
1024          *
1025          * @access public
1026          * @since 2.7.0
1027          *
1028          * @param string $url
1029          * @param str|array $args Optional. Override the defaults.
1030          * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
1031          */
1032         function request($url, $args = array()) {
1033                 $defaults = array(
1034                         'method' => 'GET', 'timeout' => 5,
1035                         'redirection' => 5, 'httpversion' => '1.0',
1036                         'blocking' => true,
1037                         'headers' => array(), 'body' => null, 'cookies' => array()
1038                 );
1039
1040                 $r = wp_parse_args( $args, $defaults );
1041
1042                 if ( isset($r['headers']['User-Agent']) ) {
1043                         $r['user-agent'] = $r['headers']['User-Agent'];
1044                         unset($r['headers']['User-Agent']);
1045                 } else if ( isset($r['headers']['user-agent']) ) {
1046                         $r['user-agent'] = $r['headers']['user-agent'];
1047                         unset($r['headers']['user-agent']);
1048                 }
1049
1050                 // Construct Cookie: header if any cookies are set.
1051                 WP_Http::buildCookieHeader( $r );
1052
1053                 $handle = curl_init();
1054
1055                 // cURL offers really easy proxy support.
1056                 $proxy = new WP_HTTP_Proxy();
1057
1058                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1059
1060                         curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1061                         curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
1062                         curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
1063
1064                         if ( $proxy->use_authentication() ) {
1065                                 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1066                                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1067                         }
1068                 }
1069
1070                 $is_local = isset($r['local']) && $r['local'];
1071                 $ssl_verify = isset($r['sslverify']) && $r['sslverify'];
1072                 if ( $is_local )
1073                         $ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify);
1074                 elseif ( ! $is_local )
1075                         $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
1076
1077                 // CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since
1078                 // a value of 0 will allow an unlimited timeout.
1079                 $timeout = (int) ceil( $r['timeout'] );
1080                 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
1081                 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
1082
1083                 curl_setopt( $handle, CURLOPT_URL, $url);
1084                 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
1085                 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
1086                 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
1087                 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
1088                 // The option doesn't work with safe mode or when open_basedir is set, and there's a
1089                 // bug #17490 with redirected POST requests, so handle redirections outside Curl.
1090                 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
1091
1092                 switch ( $r['method'] ) {
1093                         case 'HEAD':
1094                                 curl_setopt( $handle, CURLOPT_NOBODY, true );
1095                                 break;
1096                         case 'POST':
1097                                 curl_setopt( $handle, CURLOPT_POST, true );
1098                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1099                                 break;
1100                         case 'PUT':
1101                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
1102                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1103                                 break;
1104                         default:
1105                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
1106                                 if ( ! is_null( $r['body'] ) )
1107                                         curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1108                                 break;
1109                 }
1110
1111                 if ( true === $r['blocking'] )
1112                         curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
1113
1114                 curl_setopt( $handle, CURLOPT_HEADER, false );
1115
1116                 // If streaming to a file open a file handle, and setup our curl streaming handler
1117                 if ( $r['stream'] ) {
1118                         if ( ! WP_DEBUG )
1119                                 $stream_handle = @fopen( $r['filename'], 'w+' );
1120                         else
1121                                 $stream_handle = fopen( $r['filename'], 'w+' );
1122                         if ( ! $stream_handle )
1123                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
1124                         curl_setopt( $handle, CURLOPT_FILE, $stream_handle );
1125                 }
1126
1127                 if ( !empty( $r['headers'] ) ) {
1128                         // cURL expects full header strings in each element
1129                         $headers = array();
1130                         foreach ( $r['headers'] as $name => $value ) {
1131                                 $headers[] = "{$name}: $value";
1132                         }
1133                         curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
1134                 }
1135
1136                 if ( $r['httpversion'] == '1.0' )
1137                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
1138                 else
1139                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
1140
1141                 // Cookies are not handled by the HTTP API currently. Allow for plugin authors to handle it
1142                 // themselves... Although, it is somewhat pointless without some reference.
1143                 do_action_ref_array( 'http_api_curl', array(&$handle) );
1144
1145                 // We don't need to return the body, so don't. Just execute request and return.
1146                 if ( ! $r['blocking'] ) {
1147                         curl_exec( $handle );
1148                         curl_close( $handle );
1149                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
1150                 }
1151
1152                 $theResponse = curl_exec( $handle );
1153                 $theBody = '';
1154                 $theHeaders = WP_Http::processHeaders( $this->headers );
1155
1156                 if ( strlen($theResponse) > 0 && ! is_bool( $theResponse ) ) // is_bool: when using $args['stream'], curl_exec will return (bool)true
1157                         $theBody = $theResponse;
1158
1159                 // If no response
1160                 if ( 0 == strlen( $theResponse ) && empty( $theHeaders['headers'] ) ) {
1161                         if ( $curl_error = curl_error( $handle ) )
1162                                 return new WP_Error( 'http_request_failed', $curl_error );
1163                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) )
1164                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1165                 }
1166
1167                 $this->headers = '';
1168
1169                 $response = array();
1170                 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
1171                 $response['message'] = get_status_header_desc($response['code']);
1172
1173                 curl_close( $handle );
1174
1175                 if ( $r['stream'] )
1176                         fclose( $stream_handle );
1177
1178                 // See #11305 - When running under safe mode, redirection is disabled above. Handle it manually.
1179                 if ( ! empty( $theHeaders['headers']['location'] ) && 0 !== $r['_redirection'] ) { // _redirection: The requested number of redirections
1180                         if ( $r['redirection']-- > 0 ) {
1181                                 return $this->request( WP_HTTP::make_absolute_url( $theHeaders['headers']['location'], $url ), $r );
1182                         } else {
1183                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1184                         }
1185                 }
1186
1187                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
1188                         $theBody = WP_Http_Encoding::decompress( $theBody );
1189
1190                 return array( 'headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies'], 'filename' => $r['filename'] );
1191         }
1192
1193         /**
1194          * Grab the headers of the cURL request
1195          *
1196          * Each header is sent individually to this callback, so we append to the $header property for temporary storage
1197          *
1198          * @since 3.2.0
1199          * @access private
1200          * @return int
1201          */
1202         private function stream_headers( $handle, $headers ) {
1203                 $this->headers .= $headers;
1204                 return strlen( $headers );
1205         }
1206
1207         /**
1208          * Whether this class can be used for retrieving an URL.
1209          *
1210          * @static
1211          * @since 2.7.0
1212          *
1213          * @return boolean False means this class can not be used, true means it can.
1214          */
1215         public static function test( $args = array() ) {
1216                 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
1217                         return false;
1218
1219                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
1220
1221                 if ( $is_ssl ) {
1222                         $curl_version = curl_version();
1223                         if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) // Does this cURL version support SSL requests?
1224                                 return false;
1225                 }
1226
1227                 return apply_filters( 'use_curl_transport', true, $args );
1228         }
1229 }
1230
1231 /**
1232  * Adds Proxy support to the WordPress HTTP API.
1233  *
1234  * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to
1235  * enable proxy support. There are also a few filters that plugins can hook into for some of the
1236  * constants.
1237  *
1238  * Please note that only BASIC authentication is supported by most transports.
1239  * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
1240  *
1241  * The constants are as follows:
1242  * <ol>
1243  * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li>
1244  * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li>
1245  * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li>
1246  * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li>
1247  * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy.
1248  * You do not need to have localhost and the blog host in this list, because they will not be passed
1249  * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org</li>
1250  * </ol>
1251  *
1252  * An example can be as seen below.
1253  * <code>
1254  * define('WP_PROXY_HOST', '192.168.84.101');
1255  * define('WP_PROXY_PORT', '8080');
1256  * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
1257  * </code>
1258  *
1259  * @link http://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress.
1260  * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS
1261  * @since 2.8
1262  */
1263 class WP_HTTP_Proxy {
1264
1265         /**
1266          * Whether proxy connection should be used.
1267          *
1268          * @since 2.8
1269          * @use WP_PROXY_HOST
1270          * @use WP_PROXY_PORT
1271          *
1272          * @return bool
1273          */
1274         function is_enabled() {
1275                 return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT');
1276         }
1277
1278         /**
1279          * Whether authentication should be used.
1280          *
1281          * @since 2.8
1282          * @use WP_PROXY_USERNAME
1283          * @use WP_PROXY_PASSWORD
1284          *
1285          * @return bool
1286          */
1287         function use_authentication() {
1288                 return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD');
1289         }
1290
1291         /**
1292          * Retrieve the host for the proxy server.
1293          *
1294          * @since 2.8
1295          *
1296          * @return string
1297          */
1298         function host() {
1299                 if ( defined('WP_PROXY_HOST') )
1300                         return WP_PROXY_HOST;
1301
1302                 return '';
1303         }
1304
1305         /**
1306          * Retrieve the port for the proxy server.
1307          *
1308          * @since 2.8
1309          *
1310          * @return string
1311          */
1312         function port() {
1313                 if ( defined('WP_PROXY_PORT') )
1314                         return WP_PROXY_PORT;
1315
1316                 return '';
1317         }
1318
1319         /**
1320          * Retrieve the username for proxy authentication.
1321          *
1322          * @since 2.8
1323          *
1324          * @return string
1325          */
1326         function username() {
1327                 if ( defined('WP_PROXY_USERNAME') )
1328                         return WP_PROXY_USERNAME;
1329
1330                 return '';
1331         }
1332
1333         /**
1334          * Retrieve the password for proxy authentication.
1335          *
1336          * @since 2.8
1337          *
1338          * @return string
1339          */
1340         function password() {
1341                 if ( defined('WP_PROXY_PASSWORD') )
1342                         return WP_PROXY_PASSWORD;
1343
1344                 return '';
1345         }
1346
1347         /**
1348          * Retrieve authentication string for proxy authentication.
1349          *
1350          * @since 2.8
1351          *
1352          * @return string
1353          */
1354         function authentication() {
1355                 return $this->username() . ':' . $this->password();
1356         }
1357
1358         /**
1359          * Retrieve header string for proxy authentication.
1360          *
1361          * @since 2.8
1362          *
1363          * @return string
1364          */
1365         function authentication_header() {
1366                 return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() );
1367         }
1368
1369         /**
1370          * Whether URL should be sent through the proxy server.
1371          *
1372          * We want to keep localhost and the blog URL from being sent through the proxy server, because
1373          * some proxies can not handle this. We also have the constant available for defining other
1374          * hosts that won't be sent through the proxy.
1375          *
1376          * @uses WP_PROXY_BYPASS_HOSTS
1377          * @since 2.8.0
1378          *
1379          * @param string $uri URI to check.
1380          * @return bool True, to send through the proxy and false if, the proxy should not be used.
1381          */
1382         function send_through_proxy( $uri ) {
1383                 // parse_url() only handles http, https type URLs, and will emit E_WARNING on failure.
1384                 // This will be displayed on blogs, which is not reasonable.
1385                 $check = @parse_url($uri);
1386
1387                 // Malformed URL, can not process, but this could mean ssl, so let through anyway.
1388                 if ( $check === false )
1389                         return true;
1390
1391                 $home = parse_url( get_option('siteurl') );
1392
1393                 $result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home );
1394                 if ( ! is_null( $result ) )
1395                         return $result;
1396
1397                 if ( $check['host'] == 'localhost' || $check['host'] == $home['host'] )
1398                         return false;
1399
1400                 if ( !defined('WP_PROXY_BYPASS_HOSTS') )
1401                         return true;
1402
1403                 static $bypass_hosts;
1404                 static $wildcard_regex = false;
1405                 if ( null == $bypass_hosts ) {
1406                         $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS);
1407
1408                         if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) {
1409                                 $wildcard_regex = array();
1410                                 foreach ( $bypass_hosts as $host )
1411                                         $wildcard_regex[] = str_replace('\*', '[\w.]+?', preg_quote($host, '/'));
1412                                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
1413                         }
1414                 }
1415
1416                 if ( !empty($wildcard_regex) )
1417                         return !preg_match($wildcard_regex, $check['host']);
1418                 else
1419                         return !in_array( $check['host'], $bypass_hosts );
1420         }
1421 }
1422 /**
1423  * Internal representation of a single cookie.
1424  *
1425  * Returned cookies are represented using this class, and when cookies are set, if they are not
1426  * already a WP_Http_Cookie() object, then they are turned into one.
1427  *
1428  * @todo The WordPress convention is to use underscores instead of camelCase for function and method
1429  * names. Need to switch to use underscores instead for the methods.
1430  *
1431  * @package WordPress
1432  * @subpackage HTTP
1433  * @since 2.8.0
1434  */
1435 class WP_Http_Cookie {
1436
1437         /**
1438          * Cookie name.
1439          *
1440          * @since 2.8.0
1441          * @var string
1442          */
1443         var $name;
1444
1445         /**
1446          * Cookie value.
1447          *
1448          * @since 2.8.0
1449          * @var string
1450          */
1451         var $value;
1452
1453         /**
1454          * When the cookie expires.
1455          *
1456          * @since 2.8.0
1457          * @var string
1458          */
1459         var $expires;
1460
1461         /**
1462          * Cookie URL path.
1463          *
1464          * @since 2.8.0
1465          * @var string
1466          */
1467         var $path;
1468
1469         /**
1470          * Cookie Domain.
1471          *
1472          * @since 2.8.0
1473          * @var string
1474          */
1475         var $domain;
1476
1477         /**
1478          * Sets up this cookie object.
1479          *
1480          * The parameter $data should be either an associative array containing the indices names below
1481          * or a header string detailing it.
1482          *
1483          * If it's an array, it should include the following elements:
1484          * <ol>
1485          * <li>Name</li>
1486          * <li>Value - should NOT be urlencoded already.</li>
1487          * <li>Expires - (optional) String or int (UNIX timestamp).</li>
1488          * <li>Path (optional)</li>
1489          * <li>Domain (optional)</li>
1490          * </ol>
1491          *
1492          * @access public
1493          * @since 2.8.0
1494          *
1495          * @param string|array $data Raw cookie data.
1496          */
1497         function __construct( $data ) {
1498                 if ( is_string( $data ) ) {
1499                         // Assume it's a header string direct from a previous request
1500                         $pairs = explode( ';', $data );
1501
1502                         // Special handling for first pair; name=value. Also be careful of "=" in value
1503                         $name  = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) );
1504                         $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 );
1505                         $this->name  = $name;
1506                         $this->value = urldecode( $value );
1507                         array_shift( $pairs ); //Removes name=value from items.
1508
1509                         // Set everything else as a property
1510                         foreach ( $pairs as $pair ) {
1511                                 $pair = rtrim($pair);
1512                                 if ( empty($pair) ) //Handles the cookie ending in ; which results in a empty final pair
1513                                         continue;
1514
1515                                 list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' );
1516                                 $key = strtolower( trim( $key ) );
1517                                 if ( 'expires' == $key )
1518                                         $val = strtotime( $val );
1519                                 $this->$key = $val;
1520                         }
1521                 } else {
1522                         if ( !isset( $data['name'] ) )
1523                                 return false;
1524
1525                         // Set properties based directly on parameters
1526                         $this->name   = $data['name'];
1527                         $this->value  = isset( $data['value'] ) ? $data['value'] : '';
1528                         $this->path   = isset( $data['path'] ) ? $data['path'] : '';
1529                         $this->domain = isset( $data['domain'] ) ? $data['domain'] : '';
1530
1531                         if ( isset( $data['expires'] ) )
1532                                 $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] );
1533                         else
1534                                 $this->expires = null;
1535                 }
1536         }
1537
1538         /**
1539          * Confirms that it's OK to send this cookie to the URL checked against.
1540          *
1541          * Decision is based on RFC 2109/2965, so look there for details on validity.
1542          *
1543          * @access public
1544          * @since 2.8.0
1545          *
1546          * @param string $url URL you intend to send this cookie to
1547          * @return boolean true if allowed, false otherwise.
1548          */
1549         function test( $url ) {
1550                 // Expires - if expired then nothing else matters
1551                 if ( isset( $this->expires ) && time() > $this->expires )
1552                         return false;
1553
1554                 // Get details on the URL we're thinking about sending to
1555                 $url = parse_url( $url );
1556                 $url['port'] = isset( $url['port'] ) ? $url['port'] : 80;
1557                 $url['path'] = isset( $url['path'] ) ? $url['path'] : '/';
1558
1559                 // Values to use for comparison against the URL
1560                 $path   = isset( $this->path )   ? $this->path   : '/';
1561                 $port   = isset( $this->port )   ? $this->port   : 80;
1562                 $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] );
1563                 if ( false === stripos( $domain, '.' ) )
1564                         $domain .= '.local';
1565
1566                 // Host - very basic check that the request URL ends with the domain restriction (minus leading dot)
1567                 $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain;
1568                 if ( substr( $url['host'], -strlen( $domain ) ) != $domain )
1569                         return false;
1570
1571                 // Port - supports "port-lists" in the format: "80,8000,8080"
1572                 if ( !in_array( $url['port'], explode( ',', $port) ) )
1573                         return false;
1574
1575                 // Path - request path must start with path restriction
1576                 if ( substr( $url['path'], 0, strlen( $path ) ) != $path )
1577                         return false;
1578
1579                 return true;
1580         }
1581
1582         /**
1583          * Convert cookie name and value back to header string.
1584          *
1585          * @access public
1586          * @since 2.8.0
1587          *
1588          * @return string Header encoded cookie name and value.
1589          */
1590         function getHeaderValue() {
1591                 if ( ! isset( $this->name ) || ! isset( $this->value ) )
1592                         return '';
1593
1594                 return $this->name . '=' . apply_filters( 'wp_http_cookie_value', $this->value, $this->name );
1595         }
1596
1597         /**
1598          * Retrieve cookie header for usage in the rest of the WordPress HTTP API.
1599          *
1600          * @access public
1601          * @since 2.8.0
1602          *
1603          * @return string
1604          */
1605         function getFullHeader() {
1606                 return 'Cookie: ' . $this->getHeaderValue();
1607         }
1608 }
1609
1610 /**
1611  * Implementation for deflate and gzip transfer encodings.
1612  *
1613  * Includes RFC 1950, RFC 1951, and RFC 1952.
1614  *
1615  * @since 2.8
1616  * @package WordPress
1617  * @subpackage HTTP
1618  */
1619 class WP_Http_Encoding {
1620
1621         /**
1622          * Compress raw string using the deflate format.
1623          *
1624          * Supports the RFC 1951 standard.
1625          *
1626          * @since 2.8
1627          *
1628          * @param string $raw String to compress.
1629          * @param int $level Optional, default is 9. Compression level, 9 is highest.
1630          * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports.
1631          * @return string|bool False on failure.
1632          */
1633         public static function compress( $raw, $level = 9, $supports = null ) {
1634                 return gzdeflate( $raw, $level );
1635         }
1636
1637         /**
1638          * Decompression of deflated string.
1639          *
1640          * Will attempt to decompress using the RFC 1950 standard, and if that fails
1641          * then the RFC 1951 standard deflate will be attempted. Finally, the RFC
1642          * 1952 standard gzip decode will be attempted. If all fail, then the
1643          * original compressed string will be returned.
1644          *
1645          * @since 2.8
1646          *
1647          * @param string $compressed String to decompress.
1648          * @param int $length The optional length of the compressed data.
1649          * @return string|bool False on failure.
1650          */
1651         public static function decompress( $compressed, $length = null ) {
1652
1653                 if ( empty($compressed) )
1654                         return $compressed;
1655
1656                 if ( false !== ( $decompressed = @gzinflate( $compressed ) ) )
1657                         return $decompressed;
1658
1659                 if ( false !== ( $decompressed = WP_Http_Encoding::compatible_gzinflate( $compressed ) ) )
1660                         return $decompressed;
1661
1662                 if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) )
1663                         return $decompressed;
1664
1665                 if ( function_exists('gzdecode') ) {
1666                         $decompressed = @gzdecode( $compressed );
1667
1668                         if ( false !== $decompressed )
1669                                 return $decompressed;
1670                 }
1671
1672                 return $compressed;
1673         }
1674
1675         /**
1676          * Decompression of deflated string while staying compatible with the majority of servers.
1677          *
1678          * Certain Servers will return deflated data with headers which PHP's gzinflate()
1679          * function cannot handle out of the box. The following function has been created from
1680          * various snippets on the gzinflate() PHP documentation.
1681          *
1682          * Warning: Magic numbers within. Due to the potential different formats that the compressed
1683          * data may be returned in, some "magic offsets" are needed to ensure proper decompression
1684          * takes place. For a simple progmatic way to determine the magic offset in use, see:
1685          * http://core.trac.wordpress.org/ticket/18273
1686          *
1687          * @since 2.8.1
1688          * @link http://core.trac.wordpress.org/ticket/18273
1689          * @link http://au2.php.net/manual/en/function.gzinflate.php#70875
1690          * @link http://au2.php.net/manual/en/function.gzinflate.php#77336
1691          *
1692          * @param string $gzData String to decompress.
1693          * @return string|bool False on failure.
1694          */
1695         public static function compatible_gzinflate($gzData) {
1696
1697                 // Compressed data might contain a full header, if so strip it for gzinflate()
1698                 if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
1699                         $i = 10;
1700                         $flg = ord( substr($gzData, 3, 1) );
1701                         if ( $flg > 0 ) {
1702                                 if ( $flg & 4 ) {
1703                                         list($xlen) = unpack('v', substr($gzData, $i, 2) );
1704                                         $i = $i + 2 + $xlen;
1705                                 }
1706                                 if ( $flg & 8 )
1707                                         $i = strpos($gzData, "\0", $i) + 1;
1708                                 if ( $flg & 16 )
1709                                         $i = strpos($gzData, "\0", $i) + 1;
1710                                 if ( $flg & 2 )
1711                                         $i = $i + 2;
1712                         }
1713                         $decompressed = @gzinflate( substr($gzData, $i, -8) );
1714                         if ( false !== $decompressed )
1715                                 return $decompressed;
1716                 }
1717
1718                 // Compressed data from java.util.zip.Deflater amongst others.
1719                 $decompressed = @gzinflate( substr($gzData, 2) );
1720                 if ( false !== $decompressed )
1721                         return $decompressed;
1722
1723                 return false;
1724         }
1725
1726         /**
1727          * What encoding types to accept and their priority values.
1728          *
1729          * @since 2.8
1730          *
1731          * @return string Types of encoding to accept.
1732          */
1733         public static function accept_encoding() {
1734                 $type = array();
1735                 if ( function_exists( 'gzinflate' ) )
1736                         $type[] = 'deflate;q=1.0';
1737
1738                 if ( function_exists( 'gzuncompress' ) )
1739                         $type[] = 'compress;q=0.5';
1740
1741                 if ( function_exists( 'gzdecode' ) )
1742                         $type[] = 'gzip;q=0.5';
1743
1744                 return implode(', ', $type);
1745         }
1746
1747         /**
1748          * What encoding the content used when it was compressed to send in the headers.
1749          *
1750          * @since 2.8
1751          *
1752          * @return string Content-Encoding string to send in the header.
1753          */
1754         public static function content_encoding() {
1755                 return 'deflate';
1756         }
1757
1758         /**
1759          * Whether the content be decoded based on the headers.
1760          *
1761          * @since 2.8
1762          *
1763          * @param array|string $headers All of the available headers.
1764          * @return bool
1765          */
1766         public static function should_decode($headers) {
1767                 if ( is_array( $headers ) ) {
1768                         if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) )
1769                                 return true;
1770                 } else if ( is_string( $headers ) ) {
1771                         return ( stripos($headers, 'content-encoding:') !== false );
1772                 }
1773
1774                 return false;
1775         }
1776
1777         /**
1778          * Whether decompression and compression are supported by the PHP version.
1779          *
1780          * Each function is tested instead of checking for the zlib extension, to
1781          * ensure that the functions all exist in the PHP version and aren't
1782          * disabled.
1783          *
1784          * @since 2.8
1785          *
1786          * @return bool
1787          */
1788         public static function is_available() {
1789                 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );
1790         }
1791 }