]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-http.php
Wordpress 3.2
[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
102                 // Pre-parse for the HEAD checks.
103                 $args = wp_parse_args( $args );
104
105                 // By default, Head requests do not cause redirections.
106                 if ( isset($args['method']) && 'HEAD' == $args['method'] )
107                         $defaults['redirection'] = 0;
108
109                 $r = wp_parse_args( $args, $defaults );
110                 $r = apply_filters( 'http_request_args', $r, $url );
111
112                 // Certain classes decrement this, store a copy of the original value for loop purposes.
113                 $r['_redirection'] = $r['redirection'];
114
115                 // Allow plugins to short-circuit the request
116                 $pre = apply_filters( 'pre_http_request', false, $r, $url );
117                 if ( false !== $pre )
118                         return $pre;
119
120                 $arrURL = parse_url( $url );
121
122                 if ( empty( $url ) || empty( $arrURL['scheme'] ) )
123                         return new WP_Error('http_request_failed', __('A valid URL was not provided.'));
124
125                 if ( $this->block_request( $url ) )
126                         return new WP_Error( 'http_request_failed', __( 'User has blocked requests through HTTP.' ) );
127
128                 // Determine if this is a https call and pass that on to the transport functions
129                 // so that we can blacklist the transports that do not support ssl verification
130                 $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl';
131
132                 // Determine if this request is to OUR install of WordPress
133                 $homeURL = parse_url( get_bloginfo( 'url' ) );
134                 $r['local'] = $homeURL['host'] == $arrURL['host'] || 'localhost' == $arrURL['host'];
135                 unset( $homeURL );
136
137                 // If we are streaming to a file but no filename was given drop it in the WP temp dir
138                 // and pick it's name using the basename of the $url
139                 if ( $r['stream']  && empty( $r['filename'] ) )
140                         $r['filename'] = get_temp_dir() . basename( $url );
141
142                 // Force some settings if we are streaming to a file and check for existence and perms of destination directory
143                 if ( $r['stream'] ) {
144                         $r['blocking'] = true;
145                         if ( ! is_writable( dirname( $r['filename'] ) ) )
146                                 return new WP_Error( 'http_request_failed', __( 'Destination directory for file streaming does not exist or is not writable.' ) );
147                 }
148
149                 if ( is_null( $r['headers'] ) )
150                         $r['headers'] = array();
151
152                 if ( ! is_array( $r['headers'] ) ) {
153                         $processedHeaders = WP_Http::processHeaders( $r['headers'] );
154                         $r['headers'] = $processedHeaders['headers'];
155                 }
156
157                 if ( isset( $r['headers']['User-Agent'] ) ) {
158                         $r['user-agent'] = $r['headers']['User-Agent'];
159                         unset( $r['headers']['User-Agent'] );
160                 }
161
162                 if ( isset( $r['headers']['user-agent'] ) ) {
163                         $r['user-agent'] = $r['headers']['user-agent'];
164                         unset( $r['headers']['user-agent'] );
165                 }
166
167                 // Construct Cookie: header if any cookies are set
168                 WP_Http::buildCookieHeader( $r );
169
170                 if ( WP_Http_Encoding::is_available() )
171                         $r['headers']['Accept-Encoding'] = WP_Http_Encoding::accept_encoding();
172
173                 if ( empty($r['body']) ) {
174                         $r['body'] = null;
175                         // Some servers fail when sending content without the content-length header being set.
176                         // Also, to fix another bug, we only send when doing POST and PUT and the content-length
177                         // header isn't already set.
178                         if ( ($r['method'] == 'POST' || $r['method'] == 'PUT') && ! isset( $r['headers']['Content-Length'] ) )
179                                 $r['headers']['Content-Length'] = 0;
180                 } else {
181                         if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
182                                 $r['body'] = http_build_query( $r['body'], null, '&' );
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 arguements.
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 );
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         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         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         }
540 }
541
542 /**
543  * HTTP request method uses fsockopen function to retrieve the url.
544  *
545  * This would be the preferred method, but the fsockopen implementation has the most overhead of all
546  * the HTTP transport implementations.
547  *
548  * @package WordPress
549  * @subpackage HTTP
550  * @since 2.7.0
551  */
552 class WP_Http_Fsockopen {
553         /**
554          * Send a HTTP request to a URI using fsockopen().
555          *
556          * Does not support non-blocking mode.
557          *
558          * @see WP_Http::request For default options descriptions.
559          *
560          * @since 2.7
561          * @access public
562          * @param string $url URI resource.
563          * @param str|array $args Optional. Override the defaults.
564          * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
565          */
566         function request($url, $args = array()) {
567                 $defaults = array(
568                         'method' => 'GET', 'timeout' => 5,
569                         'redirection' => 5, 'httpversion' => '1.0',
570                         'blocking' => true,
571                         'headers' => array(), 'body' => null, 'cookies' => array()
572                 );
573
574                 $r = wp_parse_args( $args, $defaults );
575
576                 if ( isset($r['headers']['User-Agent']) ) {
577                         $r['user-agent'] = $r['headers']['User-Agent'];
578                         unset($r['headers']['User-Agent']);
579                 } else if ( isset($r['headers']['user-agent']) ) {
580                         $r['user-agent'] = $r['headers']['user-agent'];
581                         unset($r['headers']['user-agent']);
582                 }
583
584                 // Construct Cookie: header if any cookies are set
585                 WP_Http::buildCookieHeader( $r );
586
587                 $iError = null; // Store error number
588                 $strError = null; // Store error string
589
590                 $arrURL = parse_url($url);
591
592                 $fsockopen_host = $arrURL['host'];
593
594                 $secure_transport = false;
595
596                 if ( ! isset( $arrURL['port'] ) ) {
597                         if ( ( $arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https' ) && extension_loaded('openssl') ) {
598                                 $fsockopen_host = "ssl://$fsockopen_host";
599                                 $arrURL['port'] = 443;
600                                 $secure_transport = true;
601                         } else {
602                                 $arrURL['port'] = 80;
603                         }
604                 }
605
606                 //fsockopen has issues with 'localhost' with IPv6 with certain versions of PHP, It attempts to connect to ::1,
607                 // which fails when the server is not set up for it. For compatibility, always connect to the IPv4 address.
608                 if ( 'localhost' == strtolower($fsockopen_host) )
609                         $fsockopen_host = '127.0.0.1';
610
611                 // There are issues with the HTTPS and SSL protocols that cause errors that can be safely
612                 // ignored and should be ignored.
613                 if ( true === $secure_transport )
614                         $error_reporting = error_reporting(0);
615
616                 $startDelay = time();
617
618                 $proxy = new WP_HTTP_Proxy();
619
620                 if ( !WP_DEBUG ) {
621                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
622                                 $handle = @fsockopen( $proxy->host(), $proxy->port(), $iError, $strError, $r['timeout'] );
623                         else
624                                 $handle = @fsockopen( $fsockopen_host, $arrURL['port'], $iError, $strError, $r['timeout'] );
625                 } else {
626                         if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
627                                 $handle = fsockopen( $proxy->host(), $proxy->port(), $iError, $strError, $r['timeout'] );
628                         else
629                                 $handle = fsockopen( $fsockopen_host, $arrURL['port'], $iError, $strError, $r['timeout'] );
630                 }
631
632                 $endDelay = time();
633
634                 // If the delay is greater than the timeout then fsockopen should't be used, because it will
635                 // cause a long delay.
636                 $elapseDelay = ($endDelay-$startDelay) > $r['timeout'];
637                 if ( true === $elapseDelay )
638                         add_option( 'disable_fsockopen', $endDelay, null, true );
639
640                 if ( false === $handle )
641                         return new WP_Error('http_request_failed', $iError . ': ' . $strError);
642
643                 $timeout = (int) floor( $r['timeout'] );
644                 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
645                 stream_set_timeout( $handle, $timeout, $utimeout );
646
647                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field.
648                         $requestPath = $url;
649                 else
650                         $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
651
652                 if ( empty($requestPath) )
653                         $requestPath .= '/';
654
655                 $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
656
657                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) )
658                         $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n";
659                 else
660                         $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
661
662                 if ( isset($r['user-agent']) )
663                         $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
664
665                 if ( is_array($r['headers']) ) {
666                         foreach ( (array) $r['headers'] as $header => $headerValue )
667                                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
668                 } else {
669                         $strHeaders .= $r['headers'];
670                 }
671
672                 if ( $proxy->use_authentication() )
673                         $strHeaders .= $proxy->authentication_header() . "\r\n";
674
675                 $strHeaders .= "\r\n";
676
677                 if ( ! is_null($r['body']) )
678                         $strHeaders .= $r['body'];
679
680                 fwrite($handle, $strHeaders);
681
682                 if ( ! $r['blocking'] ) {
683                         fclose($handle);
684                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
685                 }
686
687                 $strResponse = '';
688                 $bodyStarted = false;
689
690                 // If streaming to a file setup the file handle
691                 if ( $r['stream'] ) {
692                         if ( ! WP_DEBUG )
693                                 $stream_handle = @fopen( $r['filename'], 'w+' );
694                         else
695                                 $stream_handle = fopen( $r['filename'], 'w+' );
696                         if ( ! $stream_handle )
697                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
698
699                         while ( ! feof($handle) ) {
700                                 $block = fread( $handle, 4096 );
701                                 if ( $bodyStarted ) {
702                                         fwrite( $stream_handle, $block );
703                                 } else {
704                                         $strResponse .= $block;
705                                         if ( strpos( $strResponse, "\r\n\r\n" ) ) {
706                                                 $process = WP_Http::processResponse( $strResponse );
707                                                 $bodyStarted = true;
708                                                 fwrite( $stream_handle, $process['body'] );
709                                                 unset( $strResponse );
710                                                 $process['body'] = '';
711                                         }
712                                 }
713                         }
714
715                         fclose( $stream_handle );
716
717                 } else {
718                         while ( ! feof($handle) )
719                                 $strResponse .= fread( $handle, 4096 );
720
721                         $process = WP_Http::processResponse( $strResponse );
722                         unset( $strResponse );
723                 }
724
725                 fclose( $handle );
726
727                 if ( true === $secure_transport )
728                         error_reporting($error_reporting);
729
730                 $arrHeaders = WP_Http::processHeaders( $process['headers'] );
731
732                 // If location is found, then assume redirect and redirect to location.
733                 if ( isset($arrHeaders['headers']['location']) && 0 !== $r['_redirection'] ) {
734                         if ( $r['redirection']-- > 0 ) {
735                                 return $this->request($arrHeaders['headers']['location'], $r);
736                         } else {
737                                 return new WP_Error('http_request_failed', __('Too many redirects.'));
738                         }
739                 }
740
741                 // If the body was chunk encoded, then decode it.
742                 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
743                         $process['body'] = WP_Http::chunkTransferDecode($process['body']);
744
745                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
746                         $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
747
748                 return array( 'headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] );
749         }
750
751         /**
752          * Whether this class can be used for retrieving an URL.
753          *
754          * @since 2.7.0
755          * @static
756          * @return boolean False means this class can not be used, true means it can.
757          */
758         function test( $args = array() ) {
759                 if ( ! function_exists( 'fsockopen' ) )
760                         return false;
761
762                 if ( false !== ($option = get_option( 'disable_fsockopen' )) && time()-$option < 43200 ) // 12 hours
763                         return false;
764
765                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
766
767                 if ( $is_ssl && ! extension_loaded( 'openssl' ) )
768                         return false;
769
770                 return apply_filters( 'use_fsockopen_transport', true, $args );
771         }
772 }
773
774 /**
775  * HTTP request method uses Streams to retrieve the url.
776  *
777  * Requires PHP 5.0+ and uses fopen with stream context. Requires that 'allow_url_fopen' PHP setting
778  * to be enabled.
779  *
780  * Second preferred method for getting the URL, for PHP 5.
781  *
782  * @package WordPress
783  * @subpackage HTTP
784  * @since 2.7.0
785  */
786 class WP_Http_Streams {
787         /**
788          * Send a HTTP request to a URI using streams with fopen().
789          *
790          * @access public
791          * @since 2.7.0
792          *
793          * @param string $url
794          * @param str|array $args Optional. Override the defaults.
795          * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
796          */
797         function request($url, $args = array()) {
798                 $defaults = array(
799                         'method' => 'GET', 'timeout' => 5,
800                         'redirection' => 5, 'httpversion' => '1.0',
801                         'blocking' => true,
802                         'headers' => array(), 'body' => null, 'cookies' => array()
803                 );
804
805                 $r = wp_parse_args( $args, $defaults );
806
807                 if ( isset($r['headers']['User-Agent']) ) {
808                         $r['user-agent'] = $r['headers']['User-Agent'];
809                         unset($r['headers']['User-Agent']);
810                 } else if ( isset($r['headers']['user-agent']) ) {
811                         $r['user-agent'] = $r['headers']['user-agent'];
812                         unset($r['headers']['user-agent']);
813                 }
814
815                 // Construct Cookie: header if any cookies are set
816                 WP_Http::buildCookieHeader( $r );
817
818                 $arrURL = parse_url($url);
819
820                 if ( false === $arrURL )
821                         return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
822
823                 if ( 'http' != $arrURL['scheme'] && 'https' != $arrURL['scheme'] )
824                         $url = preg_replace('|^' . preg_quote($arrURL['scheme'], '|') . '|', 'http', $url);
825
826                 // Convert Header array to string.
827                 $strHeaders = '';
828                 if ( is_array( $r['headers'] ) )
829                         foreach ( $r['headers'] as $name => $value )
830                                 $strHeaders .= "{$name}: $value\r\n";
831                 else if ( is_string( $r['headers'] ) )
832                         $strHeaders = $r['headers'];
833
834                 $is_local = isset($args['local']) && $args['local'];
835                 $ssl_verify = isset($args['sslverify']) && $args['sslverify'];
836                 if ( $is_local )
837                         $ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify);
838                 elseif ( ! $is_local )
839                         $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
840
841                 $arrContext = array('http' =>
842                         array(
843                                 'method' => strtoupper($r['method']),
844                                 'user_agent' => $r['user-agent'],
845                                 'max_redirects' => $r['redirection'] + 1, // See #11557
846                                 'protocol_version' => (float) $r['httpversion'],
847                                 'header' => $strHeaders,
848                                 'ignore_errors' => true, // Return non-200 requests.
849                                 'timeout' => $r['timeout'],
850                                 'ssl' => array(
851                                                 'verify_peer' => $ssl_verify,
852                                                 'verify_host' => $ssl_verify
853                                 )
854                         )
855                 );
856
857                 $proxy = new WP_HTTP_Proxy();
858
859                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
860                         $arrContext['http']['proxy'] = 'tcp://' . $proxy->host() . ':' . $proxy->port();
861                         $arrContext['http']['request_fulluri'] = true;
862
863                         // We only support Basic authentication so this will only work if that is what your proxy supports.
864                         if ( $proxy->use_authentication() )
865                                 $arrContext['http']['header'] .= $proxy->authentication_header() . "\r\n";
866                 }
867
868                 if ( ! empty($r['body'] ) )
869                         $arrContext['http']['content'] = $r['body'];
870
871                 $context = stream_context_create($arrContext);
872
873                 if ( !WP_DEBUG )
874                         $handle = @fopen($url, 'r', false, $context);
875                 else
876                         $handle = fopen($url, 'r', false, $context);
877
878                 if ( ! $handle )
879                         return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
880
881                 $timeout = (int) floor( $r['timeout'] );
882                 $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000;
883                 stream_set_timeout( $handle, $timeout, $utimeout );
884
885                 if ( ! $r['blocking'] ) {
886                         stream_set_blocking($handle, 0);
887                         fclose($handle);
888                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
889                 }
890
891                 if ( $r['stream'] ) {
892                         if ( ! WP_DEBUG )
893                                 $stream_handle = @fopen( $r['filename'], 'w+' );
894                         else
895                                 $stream_handle = fopen( $r['filename'], 'w+' );
896
897                         if ( ! $stream_handle )
898                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
899
900                         stream_copy_to_stream( $handle, $stream_handle );
901
902                         fclose( $stream_handle );
903                         $strResponse = '';
904                 } else {
905                         $strResponse = stream_get_contents( $handle );
906                 }
907
908                 $meta = stream_get_meta_data( $handle );
909
910                 fclose( $handle );
911
912                 $processedHeaders = array();
913                 if ( isset( $meta['wrapper_data']['headers'] ) )
914                         $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']['headers']);
915                 else
916                         $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
917
918                 // Streams does not provide an error code which we can use to see why the request stream stoped.
919                 // We can however test to see if a location header is present and return based on that.
920                 if ( isset($processedHeaders['headers']['location']) && 0 !== $args['_redirection'] )
921                         return new WP_Error('http_request_failed', __('Too many redirects.'));
922
923                 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
924                         $strResponse = WP_Http::chunkTransferDecode($strResponse);
925
926                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders['headers']) )
927                         $strResponse = WP_Http_Encoding::decompress( $strResponse );
928
929                 return array( 'headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies'], 'filename' => $r['filename'] );
930         }
931
932         /**
933          * Whether this class can be used for retrieving an URL.
934          *
935          * @static
936          * @access public
937          * @since 2.7.0
938          *
939          * @return boolean False means this class can not be used, true means it can.
940          */
941         function test( $args = array() ) {
942                 if ( ! function_exists( 'fopen' ) )
943                         return false;
944
945                 if ( ! function_exists( 'ini_get' ) || true != ini_get( 'allow_url_fopen' ) )
946                         return false;
947
948                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
949
950                 if ( $is_ssl && ! extension_loaded( 'openssl' ) )
951                         return false;
952
953                 return apply_filters( 'use_streams_transport', true, $args );
954         }
955 }
956
957 /**
958  * HTTP request method uses Curl extension to retrieve the url.
959  *
960  * Requires the Curl extension to be installed.
961  *
962  * @package WordPress
963  * @subpackage HTTP
964  * @since 2.7
965  */
966 class WP_Http_Curl {
967
968         /**
969          * Temporary header storage for use with streaming to a file.
970          *
971          * @since 3.2.0
972          * @access private
973          * @var string
974          */
975         private $headers = '';
976
977         /**
978          * Send a HTTP request to a URI using cURL extension.
979          *
980          * @access public
981          * @since 2.7.0
982          *
983          * @param string $url
984          * @param str|array $args Optional. Override the defaults.
985          * @return array 'headers', 'body', 'response', 'cookies' and 'filename' keys.
986          */
987         function request($url, $args = array()) {
988                 $defaults = array(
989                         'method' => 'GET', 'timeout' => 5,
990                         'redirection' => 5, 'httpversion' => '1.0',
991                         'blocking' => true,
992                         'headers' => array(), 'body' => null, 'cookies' => array()
993                 );
994
995                 $r = wp_parse_args( $args, $defaults );
996
997                 if ( isset($r['headers']['User-Agent']) ) {
998                         $r['user-agent'] = $r['headers']['User-Agent'];
999                         unset($r['headers']['User-Agent']);
1000                 } else if ( isset($r['headers']['user-agent']) ) {
1001                         $r['user-agent'] = $r['headers']['user-agent'];
1002                         unset($r['headers']['user-agent']);
1003                 }
1004
1005                 // Construct Cookie: header if any cookies are set.
1006                 WP_Http::buildCookieHeader( $r );
1007
1008                 $handle = curl_init();
1009
1010                 // cURL offers really easy proxy support.
1011                 $proxy = new WP_HTTP_Proxy();
1012
1013                 if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
1014
1015                         curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
1016                         curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
1017                         curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
1018
1019                         if ( $proxy->use_authentication() ) {
1020                                 curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
1021                                 curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
1022                         }
1023                 }
1024
1025                 $is_local = isset($args['local']) && $args['local'];
1026                 $ssl_verify = isset($args['sslverify']) && $args['sslverify'];
1027                 if ( $is_local )
1028                         $ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify);
1029                 elseif ( ! $is_local )
1030                         $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify);
1031
1032
1033                 // CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers.  Have to use ceil since
1034                 // a value of 0 will allow an ulimited timeout.
1035                 $timeout = (int) ceil( $r['timeout'] );
1036                 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
1037                 curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
1038
1039                 curl_setopt( $handle, CURLOPT_URL, $url);
1040                 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
1041                 curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
1042                 curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
1043                 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
1044                 curl_setopt( $handle, CURLOPT_MAXREDIRS, $r['redirection'] );
1045
1046                 switch ( $r['method'] ) {
1047                         case 'HEAD':
1048                                 curl_setopt( $handle, CURLOPT_NOBODY, true );
1049                                 break;
1050                         case 'POST':
1051                                 curl_setopt( $handle, CURLOPT_POST, true );
1052                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1053                                 break;
1054                         case 'PUT':
1055                                 curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
1056                                 curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
1057                                 break;
1058                 }
1059
1060                 if ( true === $r['blocking'] )
1061                         curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( &$this, 'stream_headers' ) );
1062
1063                 curl_setopt( $handle, CURLOPT_HEADER, false );
1064
1065                 // If streaming to a file open a file handle, and setup our curl streaming handler
1066                 if ( $r['stream'] ) {
1067                         if ( ! WP_DEBUG )
1068                                 $stream_handle = @fopen( $r['filename'], 'w+' );
1069                         else
1070                                 $stream_handle = fopen( $r['filename'], 'w+' );
1071                         if ( ! $stream_handle )
1072                                 return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
1073                         curl_setopt( $handle, CURLOPT_FILE, $stream_handle );
1074                 }
1075
1076                 // The option doesn't work with safe mode or when open_basedir is set.
1077                 if ( !ini_get('safe_mode') && !ini_get('open_basedir') && 0 !== $r['_redirection'] )
1078                         curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );
1079
1080                 if ( !empty( $r['headers'] ) ) {
1081                         // cURL expects full header strings in each element
1082                         $headers = array();
1083                         foreach ( $r['headers'] as $name => $value ) {
1084                                 $headers[] = "{$name}: $value";
1085                         }
1086                         curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
1087                 }
1088
1089                 if ( $r['httpversion'] == '1.0' )
1090                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
1091                 else
1092                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
1093
1094                 // Cookies are not handled by the HTTP API currently. Allow for plugin authors to handle it
1095                 // themselves... Although, it is somewhat pointless without some reference.
1096                 do_action_ref_array( 'http_api_curl', array(&$handle) );
1097
1098                 // We don't need to return the body, so don't. Just execute request and return.
1099                 if ( ! $r['blocking'] ) {
1100                         curl_exec( $handle );
1101                         curl_close( $handle );
1102                         return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
1103                 }
1104
1105                 $theResponse = curl_exec( $handle );
1106                 $theBody = '';
1107                 $theHeaders = WP_Http::processHeaders( $this->headers );
1108
1109                 if ( strlen($theResponse) > 0 && ! is_bool( $theResponse ) ) // is_bool: when using $args['stream'], curl_exec will return (bool)true
1110                         $theBody = $theResponse;
1111
1112                 // If no response, and It's not a HEAD request with valid headers returned
1113                 if ( 0 == strlen($theResponse) && ('HEAD' != $args['method'] || empty($this->headers)) ) {
1114                         if ( $curl_error = curl_error($handle) )
1115                                 return new WP_Error('http_request_failed', $curl_error);
1116                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) )
1117                                 return new WP_Error('http_request_failed', __('Too many redirects.'));
1118                 }
1119
1120                 unset( $this->headers );
1121
1122                 $response = array();
1123                 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
1124                 $response['message'] = get_status_header_desc($response['code']);
1125
1126                 curl_close( $handle );
1127
1128                 if ( $r['stream'] )
1129                         fclose( $stream_handle );
1130
1131                 // See #11305 - When running under safe mode, redirection is disabled above. Handle it manually.
1132                 if ( ! empty( $theHeaders['headers']['location'] ) && ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) && 0 !== $r['_redirection'] ) {
1133                         if ( $r['redirection']-- > 0 ) {
1134                                 return $this->request( $theHeaders['headers']['location'], $r );
1135                         } else {
1136                                 return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
1137                         }
1138                 }
1139
1140                 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
1141                         $theBody = WP_Http_Encoding::decompress( $theBody );
1142
1143                 return array( 'headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies'], 'filename' => $r['filename'] );
1144         }
1145
1146         /**
1147          * Grab the headers of the cURL request
1148          *
1149          * Each header is sent individually to this callback, so we append to the $header property for temporary storage
1150          *
1151          * @since 3.2.0
1152          * @access private
1153          * @return int
1154          */
1155         private function stream_headers( $handle, $headers ) {
1156                 $this->headers .= $headers;
1157                 return strlen( $headers );
1158         }
1159
1160         /**
1161          * Whether this class can be used for retrieving an URL.
1162          *
1163          * @static
1164          * @since 2.7.0
1165          *
1166          * @return boolean False means this class can not be used, true means it can.
1167          */
1168         function test( $args = array() ) {
1169                 if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
1170                         return false;
1171
1172                 $is_ssl = isset( $args['ssl'] ) && $args['ssl'];
1173
1174                 if ( $is_ssl ) {
1175                         $curl_version = curl_version();
1176                         if ( ! (CURL_VERSION_SSL & $curl_version['features']) ) // Does this cURL version support SSL requests?
1177                                 return false;
1178                 }
1179
1180                 return apply_filters( 'use_curl_transport', true, $args );
1181         }
1182 }
1183
1184 /**
1185  * Adds Proxy support to the WordPress HTTP API.
1186  *
1187  * There are caveats to proxy support. It requires that defines be made in the wp-config.php file to
1188  * enable proxy support. There are also a few filters that plugins can hook into for some of the
1189  * constants.
1190  *
1191  * Please note that only BASIC authentication is supported by most transports.
1192  * cURL MAY support more methods (such as NTLM authentication) depending on your environment.
1193  *
1194  * The constants are as follows:
1195  * <ol>
1196  * <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li>
1197  * <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li>
1198  * <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li>
1199  * <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li>
1200  * <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy.
1201  * You do not need to have localhost and the blog host in this list, because they will not be passed
1202  * through the proxy. The list should be presented in a comma separated list, wildcards using * are supported, eg. *.wordpress.org</li>
1203  * </ol>
1204  *
1205  * An example can be as seen below.
1206  * <code>
1207  * define('WP_PROXY_HOST', '192.168.84.101');
1208  * define('WP_PROXY_PORT', '8080');
1209  * define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
1210  * </code>
1211  *
1212  * @link http://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress.
1213  * @link http://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS
1214  * @since 2.8
1215  */
1216 class WP_HTTP_Proxy {
1217
1218         /**
1219          * Whether proxy connection should be used.
1220          *
1221          * @since 2.8
1222          * @use WP_PROXY_HOST
1223          * @use WP_PROXY_PORT
1224          *
1225          * @return bool
1226          */
1227         function is_enabled() {
1228                 return defined('WP_PROXY_HOST') && defined('WP_PROXY_PORT');
1229         }
1230
1231         /**
1232          * Whether authentication should be used.
1233          *
1234          * @since 2.8
1235          * @use WP_PROXY_USERNAME
1236          * @use WP_PROXY_PASSWORD
1237          *
1238          * @return bool
1239          */
1240         function use_authentication() {
1241                 return defined('WP_PROXY_USERNAME') && defined('WP_PROXY_PASSWORD');
1242         }
1243
1244         /**
1245          * Retrieve the host for the proxy server.
1246          *
1247          * @since 2.8
1248          *
1249          * @return string
1250          */
1251         function host() {
1252                 if ( defined('WP_PROXY_HOST') )
1253                         return WP_PROXY_HOST;
1254
1255                 return '';
1256         }
1257
1258         /**
1259          * Retrieve the port for the proxy server.
1260          *
1261          * @since 2.8
1262          *
1263          * @return string
1264          */
1265         function port() {
1266                 if ( defined('WP_PROXY_PORT') )
1267                         return WP_PROXY_PORT;
1268
1269                 return '';
1270         }
1271
1272         /**
1273          * Retrieve the username for proxy authentication.
1274          *
1275          * @since 2.8
1276          *
1277          * @return string
1278          */
1279         function username() {
1280                 if ( defined('WP_PROXY_USERNAME') )
1281                         return WP_PROXY_USERNAME;
1282
1283                 return '';
1284         }
1285
1286         /**
1287          * Retrieve the password for proxy authentication.
1288          *
1289          * @since 2.8
1290          *
1291          * @return string
1292          */
1293         function password() {
1294                 if ( defined('WP_PROXY_PASSWORD') )
1295                         return WP_PROXY_PASSWORD;
1296
1297                 return '';
1298         }
1299
1300         /**
1301          * Retrieve authentication string for proxy authentication.
1302          *
1303          * @since 2.8
1304          *
1305          * @return string
1306          */
1307         function authentication() {
1308                 return $this->username() . ':' . $this->password();
1309         }
1310
1311         /**
1312          * Retrieve header string for proxy authentication.
1313          *
1314          * @since 2.8
1315          *
1316          * @return string
1317          */
1318         function authentication_header() {
1319                 return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() );
1320         }
1321
1322         /**
1323          * Whether URL should be sent through the proxy server.
1324          *
1325          * We want to keep localhost and the blog URL from being sent through the proxy server, because
1326          * some proxies can not handle this. We also have the constant available for defining other
1327          * hosts that won't be sent through the proxy.
1328          *
1329          * @uses WP_PROXY_BYPASS_HOSTS
1330          * @since 2.8.0
1331          *
1332          * @param string $uri URI to check.
1333          * @return bool True, to send through the proxy and false if, the proxy should not be used.
1334          */
1335         function send_through_proxy( $uri ) {
1336                 // parse_url() only handles http, https type URLs, and will emit E_WARNING on failure.
1337                 // This will be displayed on blogs, which is not reasonable.
1338                 $check = @parse_url($uri);
1339
1340                 // Malformed URL, can not process, but this could mean ssl, so let through anyway.
1341                 if ( $check === false )
1342                         return true;
1343
1344                 $home = parse_url( get_option('siteurl') );
1345
1346                 if ( $check['host'] == 'localhost' || $check['host'] == $home['host'] )
1347                         return false;
1348
1349                 if ( !defined('WP_PROXY_BYPASS_HOSTS') )
1350                         return true;
1351
1352                 static $bypass_hosts;
1353                 static $wildcard_regex = false;
1354                 if ( null == $bypass_hosts ) {
1355                         $bypass_hosts = preg_split('|,\s*|', WP_PROXY_BYPASS_HOSTS);
1356
1357                         if ( false !== strpos(WP_PROXY_BYPASS_HOSTS, '*') ) {
1358                                 $wildcard_regex = array();
1359                                 foreach ( $bypass_hosts as $host )
1360                                         $wildcard_regex[] = str_replace('\*', '[\w.]+?', preg_quote($host, '/'));
1361                                 $wildcard_regex = '/^(' . implode('|', $wildcard_regex) . ')$/i';
1362                         }
1363                 }
1364
1365                 if ( !empty($wildcard_regex) )
1366                         return !preg_match($wildcard_regex, $check['host']);
1367                 else
1368                         return !in_array( $check['host'], $bypass_hosts );
1369         }
1370 }
1371 /**
1372  * Internal representation of a single cookie.
1373  *
1374  * Returned cookies are represented using this class, and when cookies are set, if they are not
1375  * already a WP_Http_Cookie() object, then they are turned into one.
1376  *
1377  * @todo The WordPress convention is to use underscores instead of camelCase for function and method
1378  * names. Need to switch to use underscores instead for the methods.
1379  *
1380  * @package WordPress
1381  * @subpackage HTTP
1382  * @since 2.8.0
1383  */
1384 class WP_Http_Cookie {
1385
1386         /**
1387          * Cookie name.
1388          *
1389          * @since 2.8.0
1390          * @var string
1391          */
1392         var $name;
1393
1394         /**
1395          * Cookie value.
1396          *
1397          * @since 2.8.0
1398          * @var string
1399          */
1400         var $value;
1401
1402         /**
1403          * When the cookie expires.
1404          *
1405          * @since 2.8.0
1406          * @var string
1407          */
1408         var $expires;
1409
1410         /**
1411          * Cookie URL path.
1412          *
1413          * @since 2.8.0
1414          * @var string
1415          */
1416         var $path;
1417
1418         /**
1419          * Cookie Domain.
1420          *
1421          * @since 2.8.0
1422          * @var string
1423          */
1424         var $domain;
1425
1426         /**
1427          * Sets up this cookie object.
1428          *
1429          * The parameter $data should be either an associative array containing the indices names below
1430          * or a header string detailing it.
1431          *
1432          * If it's an array, it should include the following elements:
1433          * <ol>
1434          * <li>Name</li>
1435          * <li>Value - should NOT be urlencoded already.</li>
1436          * <li>Expires - (optional) String or int (UNIX timestamp).</li>
1437          * <li>Path (optional)</li>
1438          * <li>Domain (optional)</li>
1439          * </ol>
1440          *
1441          * @access public
1442          * @since 2.8.0
1443          *
1444          * @param string|array $data Raw cookie data.
1445          */
1446         function __construct( $data ) {
1447                 if ( is_string( $data ) ) {
1448                         // Assume it's a header string direct from a previous request
1449                         $pairs = explode( ';', $data );
1450
1451                         // Special handling for first pair; name=value. Also be careful of "=" in value
1452                         $name  = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) );
1453                         $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 );
1454                         $this->name  = $name;
1455                         $this->value = urldecode( $value );
1456                         array_shift( $pairs ); //Removes name=value from items.
1457
1458                         // Set everything else as a property
1459                         foreach ( $pairs as $pair ) {
1460                                 $pair = rtrim($pair);
1461                                 if ( empty($pair) ) //Handles the cookie ending in ; which results in a empty final pair
1462                                         continue;
1463
1464                                 list( $key, $val ) = strpos( $pair, '=' ) ? explode( '=', $pair ) : array( $pair, '' );
1465                                 $key = strtolower( trim( $key ) );
1466                                 if ( 'expires' == $key )
1467                                         $val = strtotime( $val );
1468                                 $this->$key = $val;
1469                         }
1470                 } else {
1471                         if ( !isset( $data['name'] ) )
1472                                 return false;
1473
1474                         // Set properties based directly on parameters
1475                         $this->name   = $data['name'];
1476                         $this->value  = isset( $data['value'] ) ? $data['value'] : '';
1477                         $this->path   = isset( $data['path'] ) ? $data['path'] : '';
1478                         $this->domain = isset( $data['domain'] ) ? $data['domain'] : '';
1479
1480                         if ( isset( $data['expires'] ) )
1481                                 $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] );
1482                         else
1483                                 $this->expires = null;
1484                 }
1485         }
1486
1487         /**
1488          * Confirms that it's OK to send this cookie to the URL checked against.
1489          *
1490          * Decision is based on RFC 2109/2965, so look there for details on validity.
1491          *
1492          * @access public
1493          * @since 2.8.0
1494          *
1495          * @param string $url URL you intend to send this cookie to
1496          * @return boolean TRUE if allowed, FALSE otherwise.
1497          */
1498         function test( $url ) {
1499                 // Expires - if expired then nothing else matters
1500                 if ( time() > $this->expires )
1501                         return false;
1502
1503                 // Get details on the URL we're thinking about sending to
1504                 $url = parse_url( $url );
1505                 $url['port'] = isset( $url['port'] ) ? $url['port'] : 80;
1506                 $url['path'] = isset( $url['path'] ) ? $url['path'] : '/';
1507
1508                 // Values to use for comparison against the URL
1509                 $path   = isset( $this->path )   ? $this->path   : '/';
1510                 $port   = isset( $this->port )   ? $this->port   : 80;
1511                 $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] );
1512                 if ( false === stripos( $domain, '.' ) )
1513                         $domain .= '.local';
1514
1515                 // Host - very basic check that the request URL ends with the domain restriction (minus leading dot)
1516                 $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain;
1517                 if ( substr( $url['host'], -strlen( $domain ) ) != $domain )
1518                         return false;
1519
1520                 // Port - supports "port-lists" in the format: "80,8000,8080"
1521                 if ( !in_array( $url['port'], explode( ',', $port) ) )
1522                         return false;
1523
1524                 // Path - request path must start with path restriction
1525                 if ( substr( $url['path'], 0, strlen( $path ) ) != $path )
1526                         return false;
1527
1528                 return true;
1529         }
1530
1531         /**
1532          * Convert cookie name and value back to header string.
1533          *
1534          * @access public
1535          * @since 2.8.0
1536          *
1537          * @return string Header encoded cookie name and value.
1538          */
1539         function getHeaderValue() {
1540                 if ( empty( $this->name ) || empty( $this->value ) )
1541                         return '';
1542
1543                 return $this->name . '=' . urlencode( $this->value );
1544         }
1545
1546         /**
1547          * Retrieve cookie header for usage in the rest of the WordPress HTTP API.
1548          *
1549          * @access public
1550          * @since 2.8.0
1551          *
1552          * @return string
1553          */
1554         function getFullHeader() {
1555                 return 'Cookie: ' . $this->getHeaderValue();
1556         }
1557 }
1558
1559 /**
1560  * Implementation for deflate and gzip transfer encodings.
1561  *
1562  * Includes RFC 1950, RFC 1951, and RFC 1952.
1563  *
1564  * @since 2.8
1565  * @package WordPress
1566  * @subpackage HTTP
1567  */
1568 class WP_Http_Encoding {
1569
1570         /**
1571          * Compress raw string using the deflate format.
1572          *
1573          * Supports the RFC 1951 standard.
1574          *
1575          * @since 2.8
1576          *
1577          * @param string $raw String to compress.
1578          * @param int $level Optional, default is 9. Compression level, 9 is highest.
1579          * @param string $supports Optional, not used. When implemented it will choose the right compression based on what the server supports.
1580          * @return string|bool False on failure.
1581          */
1582         function compress( $raw, $level = 9, $supports = null ) {
1583                 return gzdeflate( $raw, $level );
1584         }
1585
1586         /**
1587          * Decompression of deflated string.
1588          *
1589          * Will attempt to decompress using the RFC 1950 standard, and if that fails
1590          * then the RFC 1951 standard deflate will be attempted. Finally, the RFC
1591          * 1952 standard gzip decode will be attempted. If all fail, then the
1592          * original compressed string will be returned.
1593          *
1594          * @since 2.8
1595          *
1596          * @param string $compressed String to decompress.
1597          * @param int $length The optional length of the compressed data.
1598          * @return string|bool False on failure.
1599          */
1600         function decompress( $compressed, $length = null ) {
1601
1602                 if ( empty($compressed) )
1603                         return $compressed;
1604
1605                 if ( false !== ( $decompressed = @gzinflate( $compressed ) ) )
1606                         return $decompressed;
1607
1608                 if ( false !== ( $decompressed = WP_Http_Encoding::compatible_gzinflate( $compressed ) ) )
1609                         return $decompressed;
1610
1611                 if ( false !== ( $decompressed = @gzuncompress( $compressed ) ) )
1612                         return $decompressed;
1613
1614                 if ( function_exists('gzdecode') ) {
1615                         $decompressed = @gzdecode( $compressed );
1616
1617                         if ( false !== $decompressed )
1618                                 return $decompressed;
1619                 }
1620
1621                 return $compressed;
1622         }
1623
1624         /**
1625          * Decompression of deflated string while staying compatible with the majority of servers.
1626          *
1627          * Certain Servers will return deflated data with headers which PHP's gziniflate()
1628          * function cannot handle out of the box. The following function lifted from
1629          * http://au2.php.net/manual/en/function.gzinflate.php#77336 will attempt to deflate
1630          * the various return forms used.
1631          *
1632          * @since 2.8.1
1633          * @link http://au2.php.net/manual/en/function.gzinflate.php#77336
1634          *
1635          * @param string $gzData String to decompress.
1636          * @return string|bool False on failure.
1637          */
1638         function compatible_gzinflate($gzData) {
1639                 if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
1640                         $i = 10;
1641                         $flg = ord( substr($gzData, 3, 1) );
1642                         if ( $flg > 0 ) {
1643                                 if ( $flg & 4 ) {
1644                                         list($xlen) = unpack('v', substr($gzData, $i, 2) );
1645                                         $i = $i + 2 + $xlen;
1646                                 }
1647                                 if ( $flg & 8 )
1648                                         $i = strpos($gzData, "\0", $i) + 1;
1649                                 if ( $flg & 16 )
1650                                         $i = strpos($gzData, "\0", $i) + 1;
1651                                 if ( $flg & 2 )
1652                                         $i = $i + 2;
1653                         }
1654                         return gzinflate( substr($gzData, $i, -8) );
1655                 } else {
1656                         return false;
1657                 }
1658         }
1659
1660         /**
1661          * What encoding types to accept and their priority values.
1662          *
1663          * @since 2.8
1664          *
1665          * @return string Types of encoding to accept.
1666          */
1667         function accept_encoding() {
1668                 $type = array();
1669                 if ( function_exists( 'gzinflate' ) )
1670                         $type[] = 'deflate;q=1.0';
1671
1672                 if ( function_exists( 'gzuncompress' ) )
1673                         $type[] = 'compress;q=0.5';
1674
1675                 if ( function_exists( 'gzdecode' ) )
1676                         $type[] = 'gzip;q=0.5';
1677
1678                 return implode(', ', $type);
1679         }
1680
1681         /**
1682          * What enconding the content used when it was compressed to send in the headers.
1683          *
1684          * @since 2.8
1685          *
1686          * @return string Content-Encoding string to send in the header.
1687          */
1688         function content_encoding() {
1689                 return 'deflate';
1690         }
1691
1692         /**
1693          * Whether the content be decoded based on the headers.
1694          *
1695          * @since 2.8
1696          *
1697          * @param array|string $headers All of the available headers.
1698          * @return bool
1699          */
1700         function should_decode($headers) {
1701                 if ( is_array( $headers ) ) {
1702                         if ( array_key_exists('content-encoding', $headers) && ! empty( $headers['content-encoding'] ) )
1703                                 return true;
1704                 } else if ( is_string( $headers ) ) {
1705                         return ( stripos($headers, 'content-encoding:') !== false );
1706                 }
1707
1708                 return false;
1709         }
1710
1711         /**
1712          * Whether decompression and compression are supported by the PHP version.
1713          *
1714          * Each function is tested instead of checking for the zlib extension, to
1715          * ensure that the functions all exist in the PHP version and aren't
1716          * disabled.
1717          *
1718          * @since 2.8
1719          *
1720          * @return bool
1721          */
1722         function is_available() {
1723                 return ( function_exists('gzuncompress') || function_exists('gzdeflate') || function_exists('gzinflate') );
1724         }
1725 }