Wordpress 2.7.1
[autoinstalls/wordpress.git] / wp-includes / http.php
1 <?php
2 /**
3  * Simple and uniform HTTP request API.
4  *
5  * Will eventually replace and standardize the WordPress HTTP requests made.
6  *
7  * @link http://trac.wordpress.org/ticket/4779 HTTP API Proposal
8  *
9  * @package WordPress
10  * @subpackage HTTP
11  * @since 2.7
12  * @author Jacob Santos <wordpress@santosj.name>
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 should
19  * replace Snoopy functionality, eventually. There is no available functionality
20  * to add HTTP transport implementations, since most of the HTTP transports are
21  * added and available for use.
22  *
23  * The exception is that cURL is not available as a transport and lacking an
24  * implementation. It will be added later and should be a patch on the WordPress
25  * Trac.
26  *
27  * There are no properties, because none are needed and for performance reasons.
28  * Some of the functions are static and while they do have some overhead over
29  * functions in PHP4, the purpose is maintainability. When PHP5 is finally the
30  * requirement, it will be easy to add the static keyword to the code. It is not
31  * as easy to convert a function to a method after enough code uses the old way.
32  *
33  * @package WordPress
34  * @subpackage HTTP
35  * @since 2.7
36  */
37 class WP_Http {
38
39         /**
40          * PHP4 style Constructor - Calls PHP5 Style Constructor
41          *
42          * @since 2.7
43          * @return WP_Http
44          */
45         function WP_Http() {
46                 $this->__construct();
47         }
48
49         /**
50          * PHP5 style Constructor - Setup available transport if not available.
51          *
52          * PHP4 does not have the 'self' keyword and since WordPress supports PHP4,
53          * the class needs to be used for the static call.
54          *
55          * The transport are setup to save time. This should only be called once, so
56          * the overhead should be fine.
57          *
58          * @since 2.7
59          * @return WP_Http
60          */
61         function __construct() {
62                 WP_Http::_getTransport();
63                 WP_Http::_postTransport();
64         }
65
66         /**
67          * Tests the WordPress HTTP objects for an object to use and returns it.
68          *
69          * Tests all of the objects and returns the object that passes. Also caches
70          * that object to be used later.
71          *
72          * The order for the GET/HEAD requests are Streams, HTTP Extension, Fopen,
73          * and finally Fsockopen. fsockopen() is used last, because it has the most
74          * overhead in its implementation. There isn't any real way around it, since
75          * redirects have to be supported, much the same way the other transports
76          * also handle redirects.
77          *
78          * There are currently issues with "localhost" not resolving correctly with
79          * DNS. This may cause an error "failed to open stream: A connection attempt
80          * failed because the connected party did not properly respond after a
81          * period of time, or established connection failed because connected host
82          * has failed to respond."
83          *
84          * @since 2.7
85          * @access private
86          *
87          * @param array $args Request args, default us an empty array
88          * @return object|null Null if no transports are available, HTTP transport object.
89          */
90         function &_getTransport( $args = array() ) {
91                 static $working_transport, $blocking_transport, $nonblocking_transport;
92
93                 if ( is_null($working_transport) ) {
94                         if ( true === WP_Http_ExtHttp::test() && apply_filters('use_http_extension_transport', true) ) {
95                                 $working_transport['exthttp'] = new WP_Http_ExtHttp();
96                                 $blocking_transport[] = &$working_transport['exthttp'];
97                         } else if ( true === WP_Http_Curl::test() && apply_filters('use_curl_transport', true) ) {
98                                 $working_transport['curl'] = new WP_Http_Curl();
99                                 $blocking_transport[] = &$working_transport['curl'];
100                         } else if ( true === WP_Http_Streams::test() && apply_filters('use_streams_transport', true) ) {
101                                 $working_transport['streams'] = new WP_Http_Streams();
102                                 $blocking_transport[] = &$working_transport['streams'];
103                         } else if ( true === WP_Http_Fopen::test() && apply_filters('use_fopen_transport', true) ) {
104                                 $working_transport['fopen'] = new WP_Http_Fopen();
105                                 $blocking_transport[] = &$working_transport['fopen'];
106                         } else if ( true === WP_Http_Fsockopen::test() && apply_filters('use_fsockopen_transport', true) ) {
107                                 $working_transport['fsockopen'] = new WP_Http_Fsockopen();
108                                 $blocking_transport[] = &$working_transport['fsockopen'];
109                         }
110
111                         foreach ( array('curl', 'streams', 'fopen', 'fsockopen', 'exthttp') as $transport ) {
112                                 if ( isset($working_transport[$transport]) )
113                                         $nonblocking_transport[] = &$working_transport[$transport];
114                         }
115                 }
116
117                 if ( isset($args['blocking']) && !$args['blocking'] )
118                         return $nonblocking_transport;
119                 else
120                         return $blocking_transport;
121         }
122
123         /**
124          * Tests the WordPress HTTP objects for an object to use and returns it.
125          *
126          * Tests all of the objects and returns the object that passes. Also caches
127          * that object to be used later. This is for posting content to a URL and
128          * is used when there is a body. The plain Fopen Transport can not be used
129          * to send content, but the streams transport can. This is a limitation that
130          * is addressed here, by just not including that transport.
131          *
132          * @since 2.7
133          * @access private
134          *
135          * @param array $args Request args, default us an empty array
136          * @return object|null Null if no transports are available, HTTP transport object.
137          */
138         function &_postTransport( $args = array() ) {
139                 static $working_transport, $blocking_transport, $nonblocking_transport;
140
141                 if ( is_null($working_transport) ) {
142                         if ( true === WP_Http_ExtHttp::test() && apply_filters('use_http_extension_transport', true) ) {
143                                 $working_transport['exthttp'] = new WP_Http_ExtHttp();
144                                 $blocking_transport[] = &$working_transport['exthttp'];
145                         } else if ( true === WP_Http_Streams::test() && apply_filters('use_streams_transport', true) ) {
146                                 $working_transport['streams'] = new WP_Http_Streams();
147                                 $blocking_transport[] = &$working_transport['streams'];
148                         } else if ( true === WP_Http_Fsockopen::test() && apply_filters('use_fsockopen_transport', true) ) {
149                                 $working_transport['fsockopen'] = new WP_Http_Fsockopen();
150                                 $blocking_transport[] = &$working_transport['fsockopen'];
151                         }
152
153                         foreach ( array('streams', 'fsockopen', 'exthttp') as $transport ) {
154                                 if ( isset($working_transport[$transport]) )
155                                         $nonblocking_transport[] = &$working_transport[$transport];
156                         }
157                 }
158
159                 if ( isset($args['blocking']) && !$args['blocking'] )
160                         return $nonblocking_transport;
161                 else
162                         return $blocking_transport;
163         }
164
165         /**
166          * Send a HTTP request to a URI.
167          *
168          * The body and headers are part of the arguments. The 'body' argument is
169          * for the body and will accept either a string or an array. The 'headers'
170          * argument should be an array, but a string is acceptable. If the 'body'
171          * argument is an array, then it will automatically be escaped using
172          * http_build_query().
173          *
174          * The only URI that are supported in the HTTP Transport implementation are
175          * the HTTP and HTTPS protocols. HTTP and HTTPS are assumed so the server
176          * might not know how to handle the send headers. Other protocols are
177          * unsupported and most likely will fail.
178          *
179          * The defaults are 'method', 'timeout', 'redirection', 'httpversion',
180          * 'blocking' and 'user-agent'.
181          *
182          * Accepted 'method' values are 'GET', 'POST', and 'HEAD', some transports
183          * technically allow others, but should not be assumed. The 'timeout' is
184          * used to sent how long the connection should stay open before failing when
185          * no response. 'redirection' is used to track how many redirects were taken
186          * and used to sent the amount for other transports, but not all transports
187          * accept setting that value.
188          *
189          * The 'httpversion' option is used to sent the HTTP version and accepted
190          * values are '1.0', and '1.1' and should be a string. Version 1.1 is not
191          * supported, because of chunk response. The 'user-agent' option is the
192          * user-agent and is used to replace the default user-agent, which is
193          * 'WordPress/WP_Version', where WP_Version is the value from $wp_version.
194          *
195          * 'blocking' is the default, which is used to tell the transport, whether
196          * it should halt PHP while it performs the request or continue regardless.
197          * Actually, that isn't entirely correct. Blocking mode really just means
198          * whether the fread should just pull what it can whenever it gets bytes or
199          * if it should wait until it has enough in the buffer to read or finishes
200          * reading the entire content. It doesn't actually always mean that PHP will
201          * continue going after making the request.
202          *
203          * @access public
204          * @since 2.7
205          *
206          * @param string $url URI resource.
207          * @param str|array $args Optional. Override the defaults.
208          * @return boolean
209          */
210         function request( $url, $args = array() ) {
211                 global $wp_version;
212
213                 $defaults = array(
214                         'method' => 'GET',
215                         'timeout' => apply_filters( 'http_request_timeout', 5),
216                         'redirection' => apply_filters( 'http_request_redirection_count', 5),
217                         'httpversion' => apply_filters( 'http_request_version', '1.0'),
218                         'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version ),
219                         'blocking' => true,
220                         'headers' => array(), 'body' => null
221                 );
222
223                 $r = wp_parse_args( $args, $defaults );
224                 $r = apply_filters( 'http_request_args', $r );
225
226                 if ( is_null( $r['headers'] ) )
227                         $r['headers'] = array();
228
229                 if ( ! is_array($r['headers']) ) {
230                         $processedHeaders = WP_Http::processHeaders($r['headers']);
231                         $r['headers'] = $processedHeaders['headers'];
232                 }
233
234                 if ( isset($r['headers']['User-Agent']) ) {
235                         $r['user-agent'] = $r['headers']['User-Agent'];
236                         unset($r['headers']['User-Agent']);
237                 }
238
239                 if ( isset($r['headers']['user-agent']) ) {
240                         $r['user-agent'] = $r['headers']['user-agent'];
241                         unset($r['headers']['user-agent']);
242                 }
243
244                 if ( is_null($r['body']) ) {
245                         // Some servers fail when sending content without the content-length
246                         // header being set.
247                         $r['headers']['Content-Length'] = 0;
248                         $transports = WP_Http::_getTransport($r);
249                 } else {
250                         if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
251                                 $r['body'] = http_build_query($r['body'], null, '&');
252                                 $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option('blog_charset');
253                                 $r['headers']['Content-Length'] = strlen($r['body']);
254                         }
255
256                         if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
257                                 $r['headers']['Content-Length'] = strlen($r['body']);
258
259                         $transports = WP_Http::_postTransport($r);
260                 }
261
262                 $response = array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
263                 foreach( (array) $transports as $transport ) {
264                         $response = $transport->request($url, $r);
265
266                         if( !is_wp_error($response) )
267                                 return $response;
268                 }
269
270                 return $response;
271         }
272
273         /**
274          * Uses the POST HTTP method.
275          *
276          * Used for sending data that is expected to be in the body.
277          *
278          * @access public
279          * @since 2.7
280          *
281          * @param string $url URI resource.
282          * @param str|array $args Optional. Override the defaults.
283          * @return boolean
284          */
285         function post($url, $args = array()) {
286                 $defaults = array('method' => 'POST');
287                 $r = wp_parse_args( $args, $defaults );
288                 return $this->request($url, $r);
289         }
290
291         /**
292          * Uses the GET HTTP method.
293          *
294          * Used for sending data that is expected to be in the body.
295          *
296          * @access public
297          * @since 2.7
298          *
299          * @param string $url URI resource.
300          * @param str|array $args Optional. Override the defaults.
301          * @return boolean
302          */
303         function get($url, $args = array()) {
304                 $defaults = array('method' => 'GET');
305                 $r = wp_parse_args( $args, $defaults );
306                 return $this->request($url, $r);
307         }
308
309         /**
310          * Uses the HEAD HTTP method.
311          *
312          * Used for sending data that is expected to be in the body.
313          *
314          * @access public
315          * @since 2.7
316          *
317          * @param string $url URI resource.
318          * @param str|array $args Optional. Override the defaults.
319          * @return boolean
320          */
321         function head($url, $args = array()) {
322                 $defaults = array('method' => 'HEAD');
323                 $r = wp_parse_args( $args, $defaults );
324                 return $this->request($url, $r);
325         }
326
327         /**
328          * Parses the responses and splits the parts into headers and body.
329          *
330          * @access public
331          * @static
332          * @since 2.7
333          *
334          * @param string $strResponse The full response string
335          * @return array Array with 'headers' and 'body' keys.
336          */
337         function processResponse($strResponse) {
338                 list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
339                 return array('headers' => $theHeaders, 'body' => $theBody);
340         }
341
342         /**
343          * Transform header string into an array.
344          *
345          * If an array is given then it is assumed to be raw header data with
346          * numeric keys with the headers as the values. No headers must be passed
347          * that were already processed.
348          *
349          * @access public
350          * @static
351          * @since 2.7
352          *
353          * @param string|array $headers
354          * @return array Processed string headers
355          */
356         function processHeaders($headers) {
357                 if ( is_string($headers) )
358                         $headers = explode("\n", str_replace(array("\r\n", "\r"), "\n", $headers) );
359
360                 $response = array('code' => 0, 'message' => '');
361
362                 $newheaders = array();
363                 foreach ( $headers as $tempheader ) {
364                         if ( empty($tempheader) )
365                                 continue;
366
367                         if ( false === strpos($tempheader, ':') ) {
368                                 list( , $iResponseCode, $strResponseMsg) = explode(' ', $tempheader, 3);
369                                 $response['code'] = $iResponseCode;
370                                 $response['message'] = $strResponseMsg;
371                                 continue;
372                         }
373
374                         list($key, $value) = explode(':', $tempheader, 2);
375
376                         if ( ! empty($value) )
377                                 $newheaders[strtolower($key)] = trim($value);
378                 }
379
380                 return array('response' => $response, 'headers' => $newheaders);
381         }
382
383         /**
384          * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
385          *
386          * Based off the HTTP http_encoding_dechunk function. Does not support
387          * UTF-8. Does not support returning footer headers. Shouldn't be too
388          * difficult to support it though.
389          *
390          * @todo Add support for footer chunked headers.
391          * @access public
392          * @since 2.7
393          * @static
394          *
395          * @param string $body Body content
396          * @return string Chunked decoded body on success or raw body on failure.
397          */
398         function chunkTransferDecode($body) {
399                 $body = str_replace(array("\r\n", "\r"), "\n", $body);
400                 // The body is not chunked encoding or is malformed.
401                 if ( ! preg_match( '/^[0-9a-f]+(\s|\n)+/mi', trim($body) ) )
402                         return $body;
403
404                 $parsedBody = '';
405                 //$parsedHeaders = array(); Unsupported
406
407                 while ( true ) {
408                         $hasChunk = (bool) preg_match( '/^([0-9a-f]+)(\s|\n)+/mi', $body, $match );
409
410                         if ( $hasChunk ) {
411                                 if ( empty($match[1]) )
412                                         return $body;
413
414                                 $length = hexdec( $match[1] );
415                                 $chunkLength = strlen( $match[0] );
416
417                                 $strBody = substr($body, $chunkLength, $length);
418                                 $parsedBody .= $strBody;
419
420                                 $body = ltrim(str_replace(array($match[0], $strBody), '', $body), "\n");
421
422                                 if( "0" == trim($body) )
423                                         return $parsedBody; // Ignore footer headers.
424                         } else {
425                                 return $body;
426                         }
427                 }
428         }
429 }
430
431 /**
432  * HTTP request method uses fsockopen function to retrieve the url.
433  *
434  * This would be the preferred method, but the fsockopen implementation has the
435  * most overhead of all the HTTP transport implementations.
436  *
437  * @package WordPress
438  * @subpackage HTTP
439  * @since 2.7
440  */
441 class WP_Http_Fsockopen {
442         /**
443          * Send a HTTP request to a URI using fsockopen().
444          *
445          * Does not support non-blocking mode.
446          *
447          * @see WP_Http::request For default options descriptions.
448          *
449          * @since 2.7
450          * @access public
451          * @param string $url URI resource.
452          * @param str|array $args Optional. Override the defaults.
453          * @return array 'headers', 'body', and 'response' keys.
454          */
455         function request($url, $args = array()) {
456                 $defaults = array(
457                         'method' => 'GET', 'timeout' => 5,
458                         'redirection' => 5, 'httpversion' => '1.0',
459                         'blocking' => true,
460                         'headers' => array(), 'body' => null
461                 );
462
463                 $r = wp_parse_args( $args, $defaults );
464
465                 if ( isset($r['headers']['User-Agent']) ) {
466                         $r['user-agent'] = $r['headers']['User-Agent'];
467                         unset($r['headers']['User-Agent']);
468                 } else if( isset($r['headers']['user-agent']) ) {
469                         $r['user-agent'] = $r['headers']['user-agent'];
470                         unset($r['headers']['user-agent']);
471                 }
472
473                 $iError = null; // Store error number
474                 $strError = null; // Store error string
475
476                 $arrURL = parse_url($url);
477
478                 $secure_transport = false;
479
480                 if ( ! isset($arrURL['port']) ) {
481                         if ( ($arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https') && extension_loaded('openssl') ) {
482                                 $arrURL['host'] = 'ssl://' . $arrURL['host'];
483                                 $arrURL['port'] = apply_filters('http_request_port', 443);
484                                 $secure_transport = true;
485                         } else {
486                                 $arrURL['port'] = apply_filters('http_request_default_port', 80);
487                         }
488                 } else {
489                         $arrURL['port'] = apply_filters('http_request_port', $arrURL['port']);
490                 }
491
492                 // There are issues with the HTTPS and SSL protocols that cause errors
493                 // that can be safely ignored and should be ignored.
494                 if ( true === $secure_transport )
495                         $error_reporting = error_reporting(0);
496
497                 $startDelay = time();
498
499                 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
500                         $handle = @fsockopen($arrURL['host'], $arrURL['port'], $iError, $strError, $r['timeout'] );
501                 else
502                         $handle = fsockopen($arrURL['host'], $arrURL['port'], $iError, $strError, $r['timeout'] );
503
504                 $endDelay = time();
505
506                 // If the delay is greater than the timeout then fsockopen should't be
507                 // used, because it will cause a long delay.
508                 $elapseDelay = ($endDelay-$startDelay) > $r['timeout'];
509                 if ( true === $elapseDelay )
510                         add_option( 'disable_fsockopen', $endDelay, null, true );
511
512                 if ( false === $handle )
513                         return new WP_Error('http_request_failed', $iError . ': ' . $strError);
514
515                 // WordPress supports PHP 4.3, which has this function. Removed sanity
516                 // checking for performance reasons.
517                 stream_set_timeout($handle, $r['timeout'] );
518
519                 $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
520                 $requestPath = empty($requestPath) ? '/' : $requestPath;
521
522                 $strHeaders = '';
523                 $strHeaders .= strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
524                 $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
525
526                 if( isset($r['user-agent']) )
527                         $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
528
529                 if ( is_array($r['headers']) ) {
530                         foreach ( (array) $r['headers'] as $header => $headerValue )
531                                 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
532                 } else {
533                         $strHeaders .= $r['headers'];
534                 }
535
536                 $strHeaders .= "\r\n";
537
538                 if ( ! is_null($r['body']) )
539                         $strHeaders .= $r['body'];
540
541                 fwrite($handle, $strHeaders);
542
543                 if ( ! $r['blocking'] ) {
544                         fclose($handle);
545                         return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
546                 }
547
548                 $strResponse = '';
549                 while ( ! feof($handle) )
550                         $strResponse .= fread($handle, 4096);
551
552                 fclose($handle);
553
554                 if ( true === $secure_transport )
555                         error_reporting($error_reporting);
556
557                 $process = WP_Http::processResponse($strResponse);
558                 $arrHeaders = WP_Http::processHeaders($process['headers']);
559
560                 // Is the response code within the 400 range?
561                 if ( (int) $arrHeaders['response']['code'] >= 400 && (int) $arrHeaders['response']['code'] < 500 )
562                         return new WP_Error('http_request_failed', $arrHeaders['response']['code'] . ': ' . $arrHeaders['response']['message']);
563
564                 // If location is found, then assume redirect and redirect to location.
565                 if ( isset($arrHeaders['headers']['location']) ) {
566                         if ( $r['redirection']-- > 0 ) {
567                                 return $this->request($arrHeaders['headers']['location'], $r);
568                         } else {
569                                 return new WP_Error('http_request_failed', __('Too many redirects.'));
570                         }
571                 }
572
573                 // If the body was chunk encoded, then decode it.
574                 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
575                         $process['body'] = WP_Http::chunkTransferDecode($process['body']);
576
577                 return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response']);
578         }
579
580         /**
581          * Whether this class can be used for retrieving an URL.
582          *
583          * @since 2.7
584          * @static
585          * @return boolean False means this class can not be used, true means it can.
586          */
587         function test() {
588                 if ( false !== ($option = get_option( 'disable_fsockopen' )) && time()-$option < 43200 ) // 12 hours
589                         return false;
590
591                 if ( function_exists( 'fsockopen' ) )
592                         return true;
593
594                 return false;
595         }
596 }
597
598 /**
599  * HTTP request method uses fopen function to retrieve the url.
600  *
601  * Requires PHP version greater than 4.3.0 for stream support. Does not allow
602  * for $context support, but should still be okay, to write the headers, before
603  * getting the response. Also requires that 'allow_url_fopen' to be enabled.
604  *
605  * @package WordPress
606  * @subpackage HTTP
607  * @since 2.7
608  */
609 class WP_Http_Fopen {
610         /**
611          * Send a HTTP request to a URI using fopen().
612          *
613          * This transport does not support sending of headers and body, therefore
614          * should not be used in the instances, where there is a body and headers.
615          *
616          * Notes: Does not support non-blocking mode. Ignores 'redirection' option.
617          *
618          * @see WP_Http::retrieve For default options descriptions.
619          *
620          * @access public
621          * @since 2.7
622          *
623          * @param string $url URI resource.
624          * @param str|array $args Optional. Override the defaults.
625          * @return array 'headers', 'body', and 'response' keys.
626          */
627         function request($url, $args = array()) {
628                 global $http_response_header;
629
630                 $defaults = array(
631                         'method' => 'GET', 'timeout' => 5,
632                         'redirection' => 5, 'httpversion' => '1.0',
633                         'blocking' => true,
634                         'headers' => array(), 'body' => null
635                 );
636
637                 $r = wp_parse_args( $args, $defaults );
638
639                 $arrURL = parse_url($url);
640
641                 if ( false === $arrURL )
642                         return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
643
644                 if ( 'http' != $arrURL['scheme'] && 'https' != $arrURL['scheme'] )
645                         $url = str_replace($arrURL['scheme'], 'http', $url);
646
647                 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
648                         $handle = @fopen($url, 'r');
649                 else
650                         $handle = fopen($url, 'r');
651
652                 if (! $handle)
653                         return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
654
655                 // WordPress supports PHP 4.3, which has this function. Removed sanity
656                 // checking for performance reasons.
657                 stream_set_timeout($handle, $r['timeout'] );
658
659                 if ( ! $r['blocking'] ) {
660                         fclose($handle);
661                         return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
662                 }
663
664                 $strResponse = '';
665                 while ( ! feof($handle) )
666                         $strResponse .= fread($handle, 4096);
667
668                 $theHeaders = '';
669                 if ( function_exists('stream_get_meta_data') ) {
670                         $meta = stream_get_meta_data($handle);
671                         $theHeaders = $meta['wrapper_data'];
672                         if( isset( $meta['wrapper_data']['headers'] ) )
673                                 $theHeaders = $meta['wrapper_data']['headers'];
674                 } else {
675                         if( ! isset( $http_response_header ) )
676                                 global $http_response_header;
677                         $theHeaders = $http_response_header;
678                 }
679
680                 fclose($handle);
681
682                 $processedHeaders = WP_Http::processHeaders($theHeaders);
683
684                 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
685                         $strResponse = WP_Http::chunkTransferDecode($strResponse);
686
687                 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']);
688         }
689
690         /**
691          * Whether this class can be used for retrieving an URL.
692          *
693          * @static
694          * @return boolean False means this class can not be used, true means it can.
695          */
696         function test() {
697                 if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
698                         return false;
699
700                 return true;
701         }
702 }
703
704 /**
705  * HTTP request method uses Streams to retrieve the url.
706  *
707  * Requires PHP 5.0+ and uses fopen with stream context. Requires that
708  * 'allow_url_fopen' PHP setting to be enabled.
709  *
710  * Second preferred method for getting the URL, for PHP 5.
711  *
712  * @package WordPress
713  * @subpackage HTTP
714  * @since 2.7
715  */
716 class WP_Http_Streams {
717         /**
718          * Send a HTTP request to a URI using streams with fopen().
719          *
720          * @access public
721          * @since 2.7
722          *
723          * @param string $url
724          * @param str|array $args Optional. Override the defaults.
725          * @return array 'headers', 'body', and 'response' keys.
726          */
727         function request($url, $args = array()) {
728                 $defaults = array(
729                         'method' => 'GET', 'timeout' => 5,
730                         'redirection' => 5, 'httpversion' => '1.0',
731                         'blocking' => true,
732                         'headers' => array(), 'body' => null
733                 );
734
735                 $r = wp_parse_args( $args, $defaults );
736
737                 if ( isset($r['headers']['User-Agent']) ) {
738                         $r['user-agent'] = $r['headers']['User-Agent'];
739                         unset($r['headers']['User-Agent']);
740                 } else if( isset($r['headers']['user-agent']) ) {
741                         $r['user-agent'] = $r['headers']['user-agent'];
742                         unset($r['headers']['user-agent']);
743                 }
744
745                 $arrURL = parse_url($url);
746
747                 if ( false === $arrURL )
748                         return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
749
750                 if ( 'http' != $arrURL['scheme'] && 'https' != $arrURL['scheme'] )
751                         $url = str_replace($arrURL['scheme'], 'http', $url);
752
753                 // Convert Header array to string.
754                 $strHeaders = '';
755                 if ( is_array( $r['headers'] ) )
756                         foreach( $r['headers'] as $name => $value )
757                                 $strHeaders .= "{$name}: $value\r\n";
758                 else if ( is_string( $r['headers'] ) )
759                         $strHeaders = $r['headers'];
760
761                 $arrContext = array('http' =>
762                         array(
763                                 'method' => strtoupper($r['method']),
764                                 'user_agent' => $r['user-agent'],
765                                 'max_redirects' => $r['redirection'],
766                                 'protocol_version' => (float) $r['httpversion'],
767                                 'header' => $strHeaders,
768                                 'timeout' => $r['timeout']
769                         )
770                 );
771
772                 if ( ! is_null($r['body']) && ! empty($r['body'] ) )
773                         $arrContext['http']['content'] = $r['body'];
774
775                 $context = stream_context_create($arrContext);
776
777                 if ( ! defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
778                         $handle = @fopen($url, 'r', false, $context);
779                 else
780                         $handle = fopen($url, 'r', false, $context);
781
782                 if ( ! $handle)
783                         return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
784
785                 // WordPress supports PHP 4.3, which has this function. Removed sanity
786                 // checking for performance reasons.
787                 stream_set_timeout($handle, $r['timeout'] );
788
789                 if ( ! $r['blocking'] ) {
790                         stream_set_blocking($handle, 0);
791                         fclose($handle);
792                         return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
793                 }
794
795                 $strResponse = stream_get_contents($handle);
796                 $meta = stream_get_meta_data($handle);
797
798                 $processedHeaders = array();
799                 if( isset( $meta['wrapper_data']['headers'] ) )
800                         $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']['headers']);
801                 else
802                         $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
803
804                 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
805                         $strResponse = WP_Http::chunkTransferDecode($strResponse);
806
807                 fclose($handle);
808
809                 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']);
810         }
811
812         /**
813          * Whether this class can be used for retrieving an URL.
814          *
815          * @static
816          * @access public
817          * @since 2.7
818          *
819          * @return boolean False means this class can not be used, true means it can.
820          */
821         function test() {
822                 if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
823                         return false;
824
825                 if ( version_compare(PHP_VERSION, '5.0', '<') )
826                         return false;
827
828                 return true;
829         }
830 }
831
832 /**
833  * HTTP request method uses HTTP extension to retrieve the url.
834  *
835  * Requires the HTTP extension to be installed. This would be the preferred
836  * transport since it can handle a lot of the problems that forces the others to
837  * use the HTTP version 1.0. Even if PHP 5.2+ is being used, it doesn't mean
838  * that the HTTP extension will be enabled.
839  *
840  * @package WordPress
841  * @subpackage HTTP
842  * @since 2.7
843  */
844 class WP_Http_ExtHTTP {
845         /**
846          * Send a HTTP request to a URI using HTTP extension.
847          *
848          * Does not support non-blocking.
849          *
850          * @access public
851          * @since 2.7
852          *
853          * @param string $url
854          * @param str|array $args Optional. Override the defaults.
855          * @return array 'headers', 'body', and 'response' keys.
856          */
857         function request($url, $args = array()) {
858                 $defaults = array(
859                         'method' => 'GET', 'timeout' => 5,
860                         'redirection' => 5, 'httpversion' => '1.0',
861                         'blocking' => true,
862                         'headers' => array(), 'body' => null
863                 );
864
865                 $r = wp_parse_args( $args, $defaults );
866
867                 if ( isset($r['headers']['User-Agent']) ) {
868                         $r['user-agent'] = $r['headers']['User-Agent'];
869                         unset($r['headers']['User-Agent']);
870                 } else if( isset($r['headers']['user-agent']) ) {
871                         $r['user-agent'] = $r['headers']['user-agent'];
872                         unset($r['headers']['user-agent']);
873                 }
874
875                 switch ( $r['method'] ) {
876                         case 'POST':
877                                 $r['method'] = HTTP_METH_POST;
878                                 break;
879                         case 'HEAD':
880                                 $r['method'] = HTTP_METH_HEAD;
881                                 break;
882                         case 'GET':
883                         default:
884                                 $r['method'] = HTTP_METH_GET;
885                 }
886
887                 $arrURL = parse_url($url);
888
889                 if ( 'http' != $arrURL['scheme'] || 'https' != $arrURL['scheme'] )
890                         $url = str_replace($arrURL['scheme'], 'http', $url);
891
892                 $options = array(
893                         'timeout' => $r['timeout'],
894                         'connecttimeout' => $r['timeout'],
895                         'redirect' => $r['redirection'],
896                         'useragent' => $r['user-agent'],
897                         'headers' => $r['headers'],
898                 );
899
900                 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) ) //Emits warning level notices for max redirects and timeouts
901                         $strResponse = @http_request($r['method'], $url, $r['body'], $options, $info);
902                 else
903                         $strResponse = http_request($r['method'], $url, $r['body'], $options, $info); //Emits warning level notices for max redirects and timeouts
904
905                 if ( false === $strResponse || ! empty($info['error']) ) //Error may still be set, Response may return headers or partial document, and error contains a reason the request was aborted, eg, timeout expired or max-redirects reached
906                         return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']);
907
908                 if ( ! $r['blocking'] )
909                         return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
910
911                 list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
912                 $theHeaders = WP_Http::processHeaders($theHeaders);
913
914                 if ( ! empty( $theBody ) && isset( $theHeaders['headers']['transfer-encoding'] ) && 'chunked' == $theHeaders['headers']['transfer-encoding'] ) {
915                         if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
916                                 $theBody = @http_chunked_decode($theBody);
917                         else
918                                 $theBody = http_chunked_decode($theBody);
919                 }
920
921                 $theResponse = array();
922                 $theResponse['code'] = $info['response_code'];
923                 $theResponse['message'] = get_status_header_desc($info['response_code']);
924
925                 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse);
926         }
927
928         /**
929          * Whether this class can be used for retrieving an URL.
930          *
931          * @static
932          * @since 2.7
933          *
934          * @return boolean False means this class can not be used, true means it can.
935          */
936         function test() {
937                 if ( function_exists('http_request') )
938                         return true;
939
940                 return false;
941         }
942 }
943
944 /**
945  * HTTP request method uses Curl extension to retrieve the url.
946  *
947  * Requires the Curl extension to be installed.
948  *
949  * @package WordPress
950  * @subpackage HTTP
951  * @since 2.7
952  */
953 class WP_Http_Curl {
954         /**
955          * Send a HTTP request to a URI using cURL extension.
956          *
957          * @access public
958          * @since 2.7
959          *
960          * @param string $url
961          * @param str|array $args Optional. Override the defaults.
962          * @return array 'headers', 'body', and 'response' keys.
963          */
964         function request($url, $args = array()) {
965                 $defaults = array(
966                         'method' => 'GET', 'timeout' => 5,
967                         'redirection' => 5, 'httpversion' => '1.0',
968                         'blocking' => true,
969                         'headers' => array(), 'body' => null
970                 );
971
972                 $r = wp_parse_args( $args, $defaults );
973
974                 if ( isset($r['headers']['User-Agent']) ) {
975                         $r['user-agent'] = $r['headers']['User-Agent'];
976                         unset($r['headers']['User-Agent']);
977                 } else if( isset($r['headers']['user-agent']) ) {
978                         $r['user-agent'] = $r['headers']['user-agent'];
979                         unset($r['headers']['user-agent']);
980                 }
981
982                 // cURL extension will sometimes fail when the timeout is less than 1 as
983                 // it may round down to 0, which gives it unlimited timeout.
984                 if ( $r['timeout'] > 0 && $r['timeout'] < 1 )
985                         $r['timeout'] = 1;
986
987                 $handle = curl_init();
988                 curl_setopt( $handle, CURLOPT_URL, $url);
989
990                 // The cURL extension requires that the option be set for the HEAD to
991                 // work properly.
992                 if ( 'HEAD' === $r['method'] ) {
993                         curl_setopt( $handle, CURLOPT_NOBODY, true );
994                 }
995
996                 if ( true === $r['blocking'] ) {
997                         curl_setopt( $handle, CURLOPT_HEADER, true );
998                         curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 1 );
999                 } else {
1000                         curl_setopt( $handle, CURLOPT_HEADER, false );
1001                         curl_setopt( $handle, CURLOPT_NOBODY, true );
1002                         curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 0 );
1003                 }
1004
1005                 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
1006                 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 1 );
1007                 curl_setopt( $handle, CURLOPT_TIMEOUT, $r['timeout'] );
1008                 curl_setopt( $handle, CURLOPT_MAXREDIRS, $r['redirection'] );
1009
1010                 // The option doesn't work with safe mode or when open_basedir is set.
1011                 if ( !ini_get('safe_mode') && !ini_get('open_basedir') )
1012                         curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );
1013
1014                 if( ! is_null($r['headers']) )
1015                         curl_setopt( $handle, CURLOPT_HTTPHEADER, $r['headers'] );
1016
1017                 if ( $r['httpversion'] == '1.0' )
1018                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
1019                 else
1020                         curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
1021
1022                 if ( ! $r['blocking'] ) {
1023                         curl_exec( $handle );
1024                         curl_close( $handle );
1025                         return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
1026                 }
1027
1028                 $theResponse = curl_exec( $handle );
1029
1030                 if ( !empty($theResponse) ) {
1031                         $headerLength = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
1032                         $theHeaders = trim( substr($theResponse, 0, $headerLength) );
1033                         $theBody = substr( $theResponse, $headerLength );
1034                         if ( false !== strrpos($theHeaders, "\r\n\r\n") ) {
1035                                 $headerParts = explode("\r\n\r\n", $theHeaders);
1036                                 $theHeaders = $headerParts[ count($headerParts) -1 ];
1037                         }
1038                         $theHeaders = WP_Http::processHeaders($theHeaders);
1039                 } else {
1040                         if ( $curl_error = curl_error($handle) )
1041                                 return new WP_Error('http_request_failed', $curl_error);
1042                         if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) )
1043                                 return new WP_Error('http_request_failed', __('Too many redirects.'));
1044
1045                         $theHeaders = array( 'headers' => array() );
1046                         $theBody = '';
1047                 }
1048                 $response = array();
1049                 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
1050                 $response['message'] = get_status_header_desc($response['code']);
1051
1052                 curl_close( $handle );
1053
1054                 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response);
1055         }
1056
1057         /**
1058          * Whether this class can be used for retrieving an URL.
1059          *
1060          * @static
1061          * @since 2.7
1062          *
1063          * @return boolean False means this class can not be used, true means it can.
1064          */
1065         function test() {
1066                 if ( function_exists('curl_init') && function_exists('curl_exec') )
1067                         return true;
1068
1069                 return false;
1070         }
1071 }
1072
1073 /**
1074  * Returns the initialized WP_Http Object
1075  *
1076  * @since 2.7
1077  * @access private
1078  *
1079  * @return WP_Http HTTP Transport object.
1080  */
1081 function &_wp_http_get_object() {
1082         static $http;
1083
1084         if ( is_null($http) )
1085                 $http = new WP_Http();
1086
1087         return $http;
1088 }
1089
1090 /**
1091  * Retrieve the raw response from the HTTP request.
1092  *
1093  * The array structure is a little complex.
1094  *
1095  * <code>
1096  * $res = array( 'headers' => array(), 'response' => array('code', 'message') );
1097  * </code>
1098  *
1099  * All of the headers in $res['headers'] are with the name as the key and the
1100  * value as the value. So to get the User-Agent, you would do the following.
1101  *
1102  * <code>
1103  * $user_agent = $res['headers']['user-agent'];
1104  * </code>
1105  *
1106  * The body is the raw response content and can be retrieved from $res['body'].
1107  *
1108  * This function is called first to make the request and there are other API
1109  * functions to abstract out the above convoluted setup.
1110  *
1111  * @since 2.7.0
1112  *
1113  * @param string $url Site URL to retrieve.
1114  * @param array $args Optional. Override the defaults.
1115  * @return string The body of the response
1116  */
1117 function wp_remote_request($url, $args = array()) {
1118         $objFetchSite = _wp_http_get_object();
1119         return $objFetchSite->request($url, $args);
1120 }
1121
1122 /**
1123  * Retrieve the raw response from the HTTP request using the GET method.
1124  *
1125  * @see wp_remote_request() For more information on the response array format.
1126  *
1127  * @since 2.7
1128  *
1129  * @param string $url Site URL to retrieve.
1130  * @param array $args Optional. Override the defaults.
1131  * @return string The body of the response
1132  */
1133 function wp_remote_get($url, $args = array()) {
1134         $objFetchSite = _wp_http_get_object();
1135
1136         return $objFetchSite->get($url, $args);
1137 }
1138
1139 /**
1140  * Retrieve the raw response from the HTTP request using the POST method.
1141  *
1142  * @see wp_remote_request() For more information on the response array format.
1143  *
1144  * @since 2.7
1145  *
1146  * @param string $url Site URL to retrieve.
1147  * @param array $args Optional. Override the defaults.
1148  * @return string The body of the response
1149  */
1150 function wp_remote_post($url, $args = array()) {
1151         $objFetchSite = _wp_http_get_object();
1152         return $objFetchSite->post($url, $args);
1153 }
1154
1155 /**
1156  * Retrieve the raw response from the HTTP request using the HEAD method.
1157  *
1158  * @see wp_remote_request() For more information on the response array format.
1159  *
1160  * @since 2.7
1161  *
1162  * @param string $url Site URL to retrieve.
1163  * @param array $args Optional. Override the defaults.
1164  * @return string The body of the response
1165  */
1166 function wp_remote_head($url, $args = array()) {
1167         $objFetchSite = _wp_http_get_object();
1168         return $objFetchSite->head($url, $args);
1169 }
1170
1171 /**
1172  * Retrieve only the headers from the raw response.
1173  *
1174  * @since 2.7
1175  *
1176  * @param array $response HTTP response.
1177  * @return array The headers of the response. Empty array if incorrect parameter given.
1178  */
1179 function wp_remote_retrieve_headers(&$response) {
1180         if ( ! isset($response['headers']) || ! is_array($response['headers']))
1181                 return array();
1182
1183         return $response['headers'];
1184 }
1185
1186 /**
1187  * Retrieve a single header by name from the raw response.
1188  *
1189  * @since 2.7
1190  *
1191  * @param array $response
1192  * @param string $header Header name to retrieve value from.
1193  * @return array The header value. Empty string on if incorrect parameter given.
1194  */
1195 function wp_remote_retrieve_header(&$response, $header) {
1196         if ( ! isset($response['headers']) || ! is_array($response['headers']))
1197                 return '';
1198
1199         if ( array_key_exists($header, $response['headers']) )
1200                 return $response['headers'][$header];
1201
1202         return '';
1203 }
1204
1205 /**
1206  * Retrieve only the response code from the raw response.
1207  *
1208  * Will return an empty array if incorrect parameter value is given.
1209  *
1210  * @since 2.7
1211  *
1212  * @param array $response HTTP response.
1213  * @return array The keys 'code' and 'message' give information on the response.
1214  */
1215 function wp_remote_retrieve_response_code(&$response) {
1216         if ( ! isset($response['response']) || ! is_array($response['response']))
1217                 return '';
1218
1219         return $response['response']['code'];
1220 }
1221
1222 /**
1223  * Retrieve only the response message from the raw response.
1224  *
1225  * Will return an empty array if incorrect parameter value is given.
1226  *
1227  * @since 2.7
1228  *
1229  * @param array $response HTTP response.
1230  * @return array The keys 'code' and 'message' give information on the response.
1231  */
1232 function wp_remote_retrieve_response_message(&$response) {
1233         if ( ! isset($response['response']) || ! is_array($response['response']))
1234                 return '';
1235
1236         return $response['response']['message'];
1237 }
1238
1239 /**
1240  * Retrieve only the body from the raw response.
1241  *
1242  * @since 2.7
1243  *
1244  * @param array $response HTTP response.
1245  * @return string The body of the response. Empty string if no body or incorrect parameter given.
1246  */
1247 function wp_remote_retrieve_body(&$response) {
1248         if ( ! isset($response['body']) )
1249                 return '';
1250
1251         return $response['body'];
1252 }
1253
1254 ?>