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