-
+ 2C86477AA780043C58DE3DEE3DF13CE75730377C6E2CB6819ED8F4990F1D0C2C40B505EBDD6B9D8904E7EAA15AF7D89AF158BDDB62A86228104C8B3888FCF963
mp-wp/wp-includes/http.php
(0 . 0)(1 . 1253)
97031 <?php
97032 /**
97033 * Simple and uniform HTTP request API.
97034 *
97035 * Will eventually replace and standardize the WordPress HTTP requests made.
97036 *
97037 * @link http://trac.wordpress.org/ticket/4779 HTTP API Proposal
97038 *
97039 * @package WordPress
97040 * @subpackage HTTP
97041 * @since 2.7
97042 * @author Jacob Santos <wordpress@santosj.name>
97043 */
97044
97045 /**
97046 * WordPress HTTP Class for managing HTTP Transports and making HTTP requests.
97047 *
97048 * This class is called for the functionality of making HTTP requests and should
97049 * replace Snoopy functionality, eventually. There is no available functionality
97050 * to add HTTP transport implementations, since most of the HTTP transports are
97051 * added and available for use.
97052 *
97053 * The exception is that cURL is not available as a transport and lacking an
97054 * implementation. It will be added later and should be a patch on the WordPress
97055 * Trac.
97056 *
97057 * There are no properties, because none are needed and for performance reasons.
97058 * Some of the functions are static and while they do have some overhead over
97059 * functions in PHP4, the purpose is maintainability. When PHP5 is finally the
97060 * requirement, it will be easy to add the static keyword to the code. It is not
97061 * as easy to convert a function to a method after enough code uses the old way.
97062 *
97063 * @package WordPress
97064 * @subpackage HTTP
97065 * @since 2.7
97066 */
97067 class WP_Http {
97068
97069 /**
97070 * PHP4 style Constructor - Calls PHP5 Style Constructor
97071 *
97072 * @since 2.7
97073 * @return WP_Http
97074 */
97075 function WP_Http() {
97076 $this->__construct();
97077 }
97078
97079 /**
97080 * PHP5 style Constructor - Setup available transport if not available.
97081 *
97082 * PHP4 does not have the 'self' keyword and since WordPress supports PHP4,
97083 * the class needs to be used for the static call.
97084 *
97085 * The transport are setup to save time. This should only be called once, so
97086 * the overhead should be fine.
97087 *
97088 * @since 2.7
97089 * @return WP_Http
97090 */
97091 function __construct() {
97092 WP_Http::_getTransport();
97093 WP_Http::_postTransport();
97094 }
97095
97096 /**
97097 * Tests the WordPress HTTP objects for an object to use and returns it.
97098 *
97099 * Tests all of the objects and returns the object that passes. Also caches
97100 * that object to be used later.
97101 *
97102 * The order for the GET/HEAD requests are Streams, HTTP Extension, Fopen,
97103 * and finally Fsockopen. fsockopen() is used last, because it has the most
97104 * overhead in its implementation. There isn't any real way around it, since
97105 * redirects have to be supported, much the same way the other transports
97106 * also handle redirects.
97107 *
97108 * There are currently issues with "localhost" not resolving correctly with
97109 * DNS. This may cause an error "failed to open stream: A connection attempt
97110 * failed because the connected party did not properly respond after a
97111 * period of time, or established connection failed because connected host
97112 * has failed to respond."
97113 *
97114 * @since 2.7
97115 * @access private
97116 *
97117 * @param array $args Request args, default us an empty array
97118 * @return object|null Null if no transports are available, HTTP transport object.
97119 */
97120 function &_getTransport( $args = array() ) {
97121 static $working_transport, $blocking_transport, $nonblocking_transport;
97122
97123 if ( is_null($working_transport) ) {
97124 if ( true === WP_Http_ExtHttp::test() && apply_filters('use_http_extension_transport', true) ) {
97125 $working_transport['exthttp'] = new WP_Http_ExtHttp();
97126 $blocking_transport[] = &$working_transport['exthttp'];
97127 } else if ( true === WP_Http_Curl::test() && apply_filters('use_curl_transport', true) ) {
97128 $working_transport['curl'] = new WP_Http_Curl();
97129 $blocking_transport[] = &$working_transport['curl'];
97130 } else if ( true === WP_Http_Streams::test() && apply_filters('use_streams_transport', true) ) {
97131 $working_transport['streams'] = new WP_Http_Streams();
97132 $blocking_transport[] = &$working_transport['streams'];
97133 } else if ( true === WP_Http_Fopen::test() && apply_filters('use_fopen_transport', true) ) {
97134 $working_transport['fopen'] = new WP_Http_Fopen();
97135 $blocking_transport[] = &$working_transport['fopen'];
97136 } else if ( true === WP_Http_Fsockopen::test() && apply_filters('use_fsockopen_transport', true) ) {
97137 $working_transport['fsockopen'] = new WP_Http_Fsockopen();
97138 $blocking_transport[] = &$working_transport['fsockopen'];
97139 }
97140
97141 foreach ( array('curl', 'streams', 'fopen', 'fsockopen', 'exthttp') as $transport ) {
97142 if ( isset($working_transport[$transport]) )
97143 $nonblocking_transport[] = &$working_transport[$transport];
97144 }
97145 }
97146
97147 if ( isset($args['blocking']) && !$args['blocking'] )
97148 return $nonblocking_transport;
97149 else
97150 return $blocking_transport;
97151 }
97152
97153 /**
97154 * Tests the WordPress HTTP objects for an object to use and returns it.
97155 *
97156 * Tests all of the objects and returns the object that passes. Also caches
97157 * that object to be used later. This is for posting content to a URL and
97158 * is used when there is a body. The plain Fopen Transport can not be used
97159 * to send content, but the streams transport can. This is a limitation that
97160 * is addressed here, by just not including that transport.
97161 *
97162 * @since 2.7
97163 * @access private
97164 *
97165 * @param array $args Request args, default us an empty array
97166 * @return object|null Null if no transports are available, HTTP transport object.
97167 */
97168 function &_postTransport( $args = array() ) {
97169 static $working_transport, $blocking_transport, $nonblocking_transport;
97170
97171 if ( is_null($working_transport) ) {
97172 if ( true === WP_Http_ExtHttp::test() && apply_filters('use_http_extension_transport', true) ) {
97173 $working_transport['exthttp'] = new WP_Http_ExtHttp();
97174 $blocking_transport[] = &$working_transport['exthttp'];
97175 } else if ( true === WP_Http_Streams::test() && apply_filters('use_streams_transport', true) ) {
97176 $working_transport['streams'] = new WP_Http_Streams();
97177 $blocking_transport[] = &$working_transport['streams'];
97178 } else if ( true === WP_Http_Fsockopen::test() && apply_filters('use_fsockopen_transport', true) ) {
97179 $working_transport['fsockopen'] = new WP_Http_Fsockopen();
97180 $blocking_transport[] = &$working_transport['fsockopen'];
97181 }
97182
97183 foreach ( array('streams', 'fsockopen', 'exthttp') as $transport ) {
97184 if ( isset($working_transport[$transport]) )
97185 $nonblocking_transport[] = &$working_transport[$transport];
97186 }
97187 }
97188
97189 if ( isset($args['blocking']) && !$args['blocking'] )
97190 return $nonblocking_transport;
97191 else
97192 return $blocking_transport;
97193 }
97194
97195 /**
97196 * Send a HTTP request to a URI.
97197 *
97198 * The body and headers are part of the arguments. The 'body' argument is
97199 * for the body and will accept either a string or an array. The 'headers'
97200 * argument should be an array, but a string is acceptable. If the 'body'
97201 * argument is an array, then it will automatically be escaped using
97202 * http_build_query().
97203 *
97204 * The only URI that are supported in the HTTP Transport implementation are
97205 * the HTTP and HTTPS protocols. HTTP and HTTPS are assumed so the server
97206 * might not know how to handle the send headers. Other protocols are
97207 * unsupported and most likely will fail.
97208 *
97209 * The defaults are 'method', 'timeout', 'redirection', 'httpversion',
97210 * 'blocking' and 'user-agent'.
97211 *
97212 * Accepted 'method' values are 'GET', 'POST', and 'HEAD', some transports
97213 * technically allow others, but should not be assumed. The 'timeout' is
97214 * used to sent how long the connection should stay open before failing when
97215 * no response. 'redirection' is used to track how many redirects were taken
97216 * and used to sent the amount for other transports, but not all transports
97217 * accept setting that value.
97218 *
97219 * The 'httpversion' option is used to sent the HTTP version and accepted
97220 * values are '1.0', and '1.1' and should be a string. Version 1.1 is not
97221 * supported, because of chunk response. The 'user-agent' option is the
97222 * user-agent and is used to replace the default user-agent, which is
97223 * 'WordPress/WP_Version', where WP_Version is the value from $wp_version.
97224 *
97225 * 'blocking' is the default, which is used to tell the transport, whether
97226 * it should halt PHP while it performs the request or continue regardless.
97227 * Actually, that isn't entirely correct. Blocking mode really just means
97228 * whether the fread should just pull what it can whenever it gets bytes or
97229 * if it should wait until it has enough in the buffer to read or finishes
97230 * reading the entire content. It doesn't actually always mean that PHP will
97231 * continue going after making the request.
97232 *
97233 * @access public
97234 * @since 2.7
97235 *
97236 * @param string $url URI resource.
97237 * @param str|array $args Optional. Override the defaults.
97238 * @return boolean
97239 */
97240 function request( $url, $args = array() ) {
97241 global $wp_version;
97242
97243 $defaults = array(
97244 'method' => 'GET',
97245 'timeout' => apply_filters( 'http_request_timeout', 5),
97246 'redirection' => apply_filters( 'http_request_redirection_count', 5),
97247 'httpversion' => apply_filters( 'http_request_version', '1.0'),
97248 'user-agent' => apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version ),
97249 'blocking' => true,
97250 'headers' => array(), 'body' => null
97251 );
97252
97253 $r = wp_parse_args( $args, $defaults );
97254 $r = apply_filters( 'http_request_args', $r );
97255
97256 if ( is_null( $r['headers'] ) )
97257 $r['headers'] = array();
97258
97259 if ( ! is_array($r['headers']) ) {
97260 $processedHeaders = WP_Http::processHeaders($r['headers']);
97261 $r['headers'] = $processedHeaders['headers'];
97262 }
97263
97264 if ( isset($r['headers']['User-Agent']) ) {
97265 $r['user-agent'] = $r['headers']['User-Agent'];
97266 unset($r['headers']['User-Agent']);
97267 }
97268
97269 if ( isset($r['headers']['user-agent']) ) {
97270 $r['user-agent'] = $r['headers']['user-agent'];
97271 unset($r['headers']['user-agent']);
97272 }
97273
97274 if ( is_null($r['body']) ) {
97275 $transports = WP_Http::_getTransport($r);
97276 } else {
97277 if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) {
97278 $r['body'] = http_build_query($r['body'], null, '&');
97279 $r['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=' . get_option('blog_charset');
97280 $r['headers']['Content-Length'] = strlen($r['body']);
97281 }
97282
97283 if ( ! isset( $r['headers']['Content-Length'] ) && ! isset( $r['headers']['content-length'] ) )
97284 $r['headers']['Content-Length'] = strlen($r['body']);
97285
97286 $transports = WP_Http::_postTransport($r);
97287 }
97288
97289 $response = array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
97290 foreach( (array) $transports as $transport ) {
97291 $response = $transport->request($url, $r);
97292
97293 if( !is_wp_error($response) )
97294 return $response;
97295 }
97296
97297 return $response;
97298 }
97299
97300 /**
97301 * Uses the POST HTTP method.
97302 *
97303 * Used for sending data that is expected to be in the body.
97304 *
97305 * @access public
97306 * @since 2.7
97307 *
97308 * @param string $url URI resource.
97309 * @param str|array $args Optional. Override the defaults.
97310 * @return boolean
97311 */
97312 function post($url, $args = array()) {
97313 $defaults = array('method' => 'POST');
97314 $r = wp_parse_args( $args, $defaults );
97315 return $this->request($url, $r);
97316 }
97317
97318 /**
97319 * Uses the GET HTTP method.
97320 *
97321 * Used for sending data that is expected to be in the body.
97322 *
97323 * @access public
97324 * @since 2.7
97325 *
97326 * @param string $url URI resource.
97327 * @param str|array $args Optional. Override the defaults.
97328 * @return boolean
97329 */
97330 function get($url, $args = array()) {
97331 $defaults = array('method' => 'GET');
97332 $r = wp_parse_args( $args, $defaults );
97333 return $this->request($url, $r);
97334 }
97335
97336 /**
97337 * Uses the HEAD HTTP method.
97338 *
97339 * Used for sending data that is expected to be in the body.
97340 *
97341 * @access public
97342 * @since 2.7
97343 *
97344 * @param string $url URI resource.
97345 * @param str|array $args Optional. Override the defaults.
97346 * @return boolean
97347 */
97348 function head($url, $args = array()) {
97349 $defaults = array('method' => 'HEAD');
97350 $r = wp_parse_args( $args, $defaults );
97351 return $this->request($url, $r);
97352 }
97353
97354 /**
97355 * Parses the responses and splits the parts into headers and body.
97356 *
97357 * @access public
97358 * @static
97359 * @since 2.7
97360 *
97361 * @param string $strResponse The full response string
97362 * @return array Array with 'headers' and 'body' keys.
97363 */
97364 function processResponse($strResponse) {
97365 list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
97366 return array('headers' => $theHeaders, 'body' => $theBody);
97367 }
97368
97369 /**
97370 * Transform header string into an array.
97371 *
97372 * If an array is given then it is assumed to be raw header data with
97373 * numeric keys with the headers as the values. No headers must be passed
97374 * that were already processed.
97375 *
97376 * @access public
97377 * @static
97378 * @since 2.7
97379 *
97380 * @param string|array $headers
97381 * @return array Processed string headers
97382 */
97383 function processHeaders($headers) {
97384 if ( is_string($headers) )
97385 $headers = explode("\n", str_replace(array("\r\n", "\r"), "\n", $headers) );
97386
97387 $response = array('code' => 0, 'message' => '');
97388
97389 $newheaders = array();
97390 foreach ( $headers as $tempheader ) {
97391 if ( empty($tempheader) )
97392 continue;
97393
97394 if ( false === strpos($tempheader, ':') ) {
97395 list( , $iResponseCode, $strResponseMsg) = explode(' ', $tempheader, 3);
97396 $response['code'] = $iResponseCode;
97397 $response['message'] = $strResponseMsg;
97398 continue;
97399 }
97400
97401 list($key, $value) = explode(':', $tempheader, 2);
97402
97403 if ( ! empty($value) )
97404 $newheaders[strtolower($key)] = trim($value);
97405 }
97406
97407 return array('response' => $response, 'headers' => $newheaders);
97408 }
97409
97410 /**
97411 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
97412 *
97413 * Based off the HTTP http_encoding_dechunk function. Does not support
97414 * UTF-8. Does not support returning footer headers. Shouldn't be too
97415 * difficult to support it though.
97416 *
97417 * @todo Add support for footer chunked headers.
97418 * @access public
97419 * @since 2.7
97420 * @static
97421 *
97422 * @param string $body Body content
97423 * @return bool|string|WP_Error False if not chunked encoded. WP_Error on failure. Chunked decoded body on success.
97424 */
97425 function chunkTransferDecode($body) {
97426 $body = str_replace(array("\r\n", "\r"), "\n", $body);
97427 // The body is not chunked encoding or is malformed.
97428 if ( ! preg_match( '/^[0-9a-f]+(\s|\n)+/mi', trim($body) ) )
97429 return $body;
97430
97431 $parsedBody = '';
97432 //$parsedHeaders = array(); Unsupported
97433
97434 $done = false;
97435
97436 do {
97437 $hasChunk = (bool) preg_match( '/^([0-9a-f]+)(\s|\n)+/mi', $body, $match );
97438
97439 if ( $hasChunk ) {
97440 if ( empty($match[1]) ) {
97441 return new WP_Error('http_chunked_decode', __('Does not appear to be chunked encoded or body is malformed.') );
97442 }
97443
97444 $length = hexdec( $match[1] );
97445 $chunkLength = strlen( $match[0] );
97446
97447 $strBody = substr($body, $chunkLength, $length);
97448 $parsedBody .= $strBody;
97449
97450 $body = ltrim(str_replace(array($match[0], $strBody), '', $body), "\n");
97451
97452 if( "0" == trim($body) ) {
97453 $done = true;
97454 return $parsedBody; // Ignore footer headers.
97455 break;
97456 }
97457 } else {
97458 return new WP_Error('http_chunked_decode', __('Does not appear to be chunked encoded or body is malformed.') );
97459 }
97460 } while ( false === $done );
97461 }
97462 }
97463
97464 /**
97465 * HTTP request method uses fsockopen function to retrieve the url.
97466 *
97467 * This would be the preferred method, but the fsockopen implementation has the
97468 * most overhead of all the HTTP transport implementations.
97469 *
97470 * @package WordPress
97471 * @subpackage HTTP
97472 * @since 2.7
97473 */
97474 class WP_Http_Fsockopen {
97475 /**
97476 * Send a HTTP request to a URI using fsockopen().
97477 *
97478 * Does not support non-blocking mode.
97479 *
97480 * @see WP_Http::request For default options descriptions.
97481 *
97482 * @since 2.7
97483 * @access public
97484 * @param string $url URI resource.
97485 * @param str|array $args Optional. Override the defaults.
97486 * @return array 'headers', 'body', and 'response' keys.
97487 */
97488 function request($url, $args = array()) {
97489 $defaults = array(
97490 'method' => 'GET', 'timeout' => 5,
97491 'redirection' => 5, 'httpversion' => '1.0',
97492 'blocking' => true,
97493 'headers' => array(), 'body' => null
97494 );
97495
97496 $r = wp_parse_args( $args, $defaults );
97497
97498 if ( isset($r['headers']['User-Agent']) ) {
97499 $r['user-agent'] = $r['headers']['User-Agent'];
97500 unset($r['headers']['User-Agent']);
97501 } else if( isset($r['headers']['user-agent']) ) {
97502 $r['user-agent'] = $r['headers']['user-agent'];
97503 unset($r['headers']['user-agent']);
97504 }
97505
97506 $iError = null; // Store error number
97507 $strError = null; // Store error string
97508
97509 $arrURL = parse_url($url);
97510
97511 $secure_transport = false;
97512
97513 if ( ! isset($arrURL['port']) ) {
97514 if ( ($arrURL['scheme'] == 'ssl' || $arrURL['scheme'] == 'https') && extension_loaded('openssl') ) {
97515 $arrURL['host'] = 'ssl://' . $arrURL['host'];
97516 $arrURL['port'] = apply_filters('http_request_port', 443);
97517 $secure_transport = true;
97518 } else {
97519 $arrURL['port'] = apply_filters('http_request_default_port', 80);
97520 }
97521 } else {
97522 $arrURL['port'] = apply_filters('http_request_port', $arrURL['port']);
97523 }
97524
97525 // There are issues with the HTTPS and SSL protocols that cause errors
97526 // that can be safely ignored and should be ignored.
97527 if ( true === $secure_transport )
97528 $error_reporting = error_reporting(0);
97529
97530 $startDelay = time();
97531
97532 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
97533 $handle = @fsockopen($arrURL['host'], $arrURL['port'], $iError, $strError, $r['timeout'] );
97534 else
97535 $handle = fsockopen($arrURL['host'], $arrURL['port'], $iError, $strError, $r['timeout'] );
97536
97537 $endDelay = time();
97538
97539 // If the delay is greater than the timeout then fsockopen should't be
97540 // used, because it will cause a long delay.
97541 $elapseDelay = ($endDelay-$startDelay) > $r['timeout'];
97542 if ( true === $elapseDelay )
97543 add_option( 'disable_fsockopen', $endDelay, null, true );
97544
97545 if ( false === $handle )
97546 return new WP_Error('http_request_failed', $iError . ': ' . $strError);
97547
97548 // WordPress supports PHP 4.3, which has this function. Removed sanity
97549 // checking for performance reasons.
97550 stream_set_timeout($handle, $r['timeout'] );
97551
97552 $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
97553 $requestPath = empty($requestPath) ? '/' : $requestPath;
97554
97555 $strHeaders = '';
97556 $strHeaders .= strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
97557 $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
97558
97559 if( isset($r['user-agent']) )
97560 $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
97561
97562 if ( is_array($r['headers']) ) {
97563 foreach ( (array) $r['headers'] as $header => $headerValue )
97564 $strHeaders .= $header . ': ' . $headerValue . "\r\n";
97565 } else {
97566 $strHeaders .= $r['headers'];
97567 }
97568
97569 $strHeaders .= "\r\n";
97570
97571 if ( ! is_null($r['body']) )
97572 $strHeaders .= $r['body'];
97573
97574 fwrite($handle, $strHeaders);
97575
97576 if ( ! $r['blocking'] ) {
97577 fclose($handle);
97578 return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
97579 }
97580
97581 $strResponse = '';
97582 while ( ! feof($handle) )
97583 $strResponse .= fread($handle, 4096);
97584
97585 fclose($handle);
97586
97587 if ( true === $secure_transport )
97588 error_reporting($error_reporting);
97589
97590 $process = WP_Http::processResponse($strResponse);
97591 $arrHeaders = WP_Http::processHeaders($process['headers']);
97592
97593 // Is the response code within the 400 range?
97594 if ( (int) $arrHeaders['response']['code'] >= 400 && (int) $arrHeaders['response']['code'] < 500 )
97595 return new WP_Error('http_request_failed', $arrHeaders['response']['code'] . ': ' . $arrHeaders['response']['message']);
97596
97597 // If location is found, then assume redirect and redirect to location.
97598 if ( isset($arrHeaders['headers']['location']) ) {
97599 if ( $r['redirection']-- > 0 ) {
97600 return $this->request($arrHeaders['headers']['location'], $r);
97601 } else {
97602 return new WP_Error('http_request_failed', __('Too many redirects.'));
97603 }
97604 }
97605
97606 // If the body was chunk encoded, then decode it.
97607 if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] )
97608 $process['body'] = WP_Http::chunkTransferDecode($process['body']);
97609
97610 return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response']);
97611 }
97612
97613 /**
97614 * Whether this class can be used for retrieving an URL.
97615 *
97616 * @since 2.7
97617 * @static
97618 * @return boolean False means this class can not be used, true means it can.
97619 */
97620 function test() {
97621 if ( false !== ($option = get_option( 'disable_fsockopen' )) && time()-$option < 43200 ) // 12 hours
97622 return false;
97623
97624 if ( function_exists( 'fsockopen' ) )
97625 return true;
97626
97627 return false;
97628 }
97629 }
97630
97631 /**
97632 * HTTP request method uses fopen function to retrieve the url.
97633 *
97634 * Requires PHP version greater than 4.3.0 for stream support. Does not allow
97635 * for $context support, but should still be okay, to write the headers, before
97636 * getting the response. Also requires that 'allow_url_fopen' to be enabled.
97637 *
97638 * @package WordPress
97639 * @subpackage HTTP
97640 * @since 2.7
97641 */
97642 class WP_Http_Fopen {
97643 /**
97644 * Send a HTTP request to a URI using fopen().
97645 *
97646 * This transport does not support sending of headers and body, therefore
97647 * should not be used in the instances, where there is a body and headers.
97648 *
97649 * Notes: Does not support non-blocking mode. Ignores 'redirection' option.
97650 *
97651 * @see WP_Http::retrieve For default options descriptions.
97652 *
97653 * @access public
97654 * @since 2.7
97655 *
97656 * @param string $url URI resource.
97657 * @param str|array $args Optional. Override the defaults.
97658 * @return array 'headers', 'body', and 'response' keys.
97659 */
97660 function request($url, $args = array()) {
97661 global $http_response_header;
97662
97663 $defaults = array(
97664 'method' => 'GET', 'timeout' => 5,
97665 'redirection' => 5, 'httpversion' => '1.0',
97666 'blocking' => true,
97667 'headers' => array(), 'body' => null
97668 );
97669
97670 $r = wp_parse_args( $args, $defaults );
97671
97672 $arrURL = parse_url($url);
97673
97674 if ( false === $arrURL )
97675 return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
97676
97677 if ( 'http' != $arrURL['scheme'] || 'https' != $arrURL['scheme'] )
97678 $url = str_replace($arrURL['scheme'], 'http', $url);
97679
97680 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
97681 $handle = @fopen($url, 'r');
97682 else
97683 $handle = fopen($url, 'r');
97684
97685 if (! $handle)
97686 return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
97687
97688 // WordPress supports PHP 4.3, which has this function. Removed sanity
97689 // checking for performance reasons.
97690 stream_set_timeout($handle, $r['timeout'] );
97691
97692 if ( ! $r['blocking'] ) {
97693 fclose($handle);
97694 return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
97695 }
97696
97697 $strResponse = '';
97698 while ( ! feof($handle) )
97699 $strResponse .= fread($handle, 4096);
97700
97701 $theHeaders = '';
97702 if ( function_exists('stream_get_meta_data') ) {
97703 $meta = stream_get_meta_data($handle);
97704 $theHeaders = $meta['wrapper_data'];
97705 if( isset( $meta['wrapper_data']['headers'] ) )
97706 $theHeaders = $meta['wrapper_data']['headers'];
97707 } else {
97708 if( ! isset( $http_response_header ) )
97709 global $http_response_header;
97710 $theHeaders = $http_response_header;
97711 }
97712
97713 fclose($handle);
97714
97715 $processedHeaders = WP_Http::processHeaders($theHeaders);
97716
97717 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
97718 $strResponse = WP_Http::chunkTransferDecode($strResponse);
97719
97720 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']);
97721 }
97722
97723 /**
97724 * Whether this class can be used for retrieving an URL.
97725 *
97726 * @static
97727 * @return boolean False means this class can not be used, true means it can.
97728 */
97729 function test() {
97730 if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
97731 return false;
97732
97733 return true;
97734 }
97735 }
97736
97737 /**
97738 * HTTP request method uses Streams to retrieve the url.
97739 *
97740 * Requires PHP 5.0+ and uses fopen with stream context. Requires that
97741 * 'allow_url_fopen' PHP setting to be enabled.
97742 *
97743 * Second preferred method for getting the URL, for PHP 5.
97744 *
97745 * @package WordPress
97746 * @subpackage HTTP
97747 * @since 2.7
97748 */
97749 class WP_Http_Streams {
97750 /**
97751 * Send a HTTP request to a URI using streams with fopen().
97752 *
97753 * @access public
97754 * @since 2.7
97755 *
97756 * @param string $url
97757 * @param str|array $args Optional. Override the defaults.
97758 * @return array 'headers', 'body', and 'response' keys.
97759 */
97760 function request($url, $args = array()) {
97761 $defaults = array(
97762 'method' => 'GET', 'timeout' => 5,
97763 'redirection' => 5, 'httpversion' => '1.0',
97764 'blocking' => true,
97765 'headers' => array(), 'body' => null
97766 );
97767
97768 $r = wp_parse_args( $args, $defaults );
97769
97770 if ( isset($r['headers']['User-Agent']) ) {
97771 $r['user-agent'] = $r['headers']['User-Agent'];
97772 unset($r['headers']['User-Agent']);
97773 } else if( isset($r['headers']['user-agent']) ) {
97774 $r['user-agent'] = $r['headers']['user-agent'];
97775 unset($r['headers']['user-agent']);
97776 }
97777
97778 $arrURL = parse_url($url);
97779
97780 if ( false === $arrURL )
97781 return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url));
97782
97783 if ( 'http' != $arrURL['scheme'] || 'https' != $arrURL['scheme'] )
97784 $url = str_replace($arrURL['scheme'], 'http', $url);
97785
97786 // Convert Header array to string.
97787 $strHeaders = '';
97788 if ( is_array( $r['headers'] ) )
97789 foreach( $r['headers'] as $name => $value )
97790 $strHeaders .= "{$name}: $value\r\n";
97791 else if ( is_string( $r['headers'] ) )
97792 $strHeaders = $r['headers'];
97793
97794 $arrContext = array('http' =>
97795 array(
97796 'method' => strtoupper($r['method']),
97797 'user_agent' => $r['user-agent'],
97798 'max_redirects' => $r['redirection'],
97799 'protocol_version' => (float) $r['httpversion'],
97800 'header' => $strHeaders,
97801 'timeout' => $r['timeout']
97802 )
97803 );
97804
97805 if ( ! is_null($r['body']) && ! empty($r['body'] ) )
97806 $arrContext['http']['content'] = $r['body'];
97807
97808 $context = stream_context_create($arrContext);
97809
97810 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
97811 $handle = @fopen($url, 'r', false, $context);
97812 else
97813 $handle = fopen($url, 'r', false, $context);
97814
97815 if ( ! $handle)
97816 return new WP_Error('http_request_failed', sprintf(__('Could not open handle for fopen() to %s'), $url));
97817
97818 // WordPress supports PHP 4.3, which has this function. Removed sanity
97819 // checking for performance reasons.
97820 stream_set_timeout($handle, $r['timeout'] );
97821
97822 if ( ! $r['blocking'] ) {
97823 stream_set_blocking($handle, 0);
97824 fclose($handle);
97825 return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
97826 }
97827
97828 $strResponse = stream_get_contents($handle);
97829 $meta = stream_get_meta_data($handle);
97830
97831 $processedHeaders = array();
97832 if( isset( $meta['wrapper_data']['headers'] ) )
97833 $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']['headers']);
97834 else
97835 $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']);
97836
97837 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] )
97838 $strResponse = WP_Http::chunkTransferDecode($strResponse);
97839
97840 fclose($handle);
97841
97842 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']);
97843 }
97844
97845 /**
97846 * Whether this class can be used for retrieving an URL.
97847 *
97848 * @static
97849 * @access public
97850 * @since 2.7
97851 *
97852 * @return boolean False means this class can not be used, true means it can.
97853 */
97854 function test() {
97855 if ( ! function_exists('fopen') || (function_exists('ini_get') && true != ini_get('allow_url_fopen')) )
97856 return false;
97857
97858 if ( version_compare(PHP_VERSION, '5.0', '<') )
97859 return false;
97860
97861 return true;
97862 }
97863 }
97864
97865 /**
97866 * HTTP request method uses HTTP extension to retrieve the url.
97867 *
97868 * Requires the HTTP extension to be installed. This would be the preferred
97869 * transport since it can handle a lot of the problems that forces the others to
97870 * use the HTTP version 1.0. Even if PHP 5.2+ is being used, it doesn't mean
97871 * that the HTTP extension will be enabled.
97872 *
97873 * @package WordPress
97874 * @subpackage HTTP
97875 * @since 2.7
97876 */
97877 class WP_Http_ExtHTTP {
97878 /**
97879 * Send a HTTP request to a URI using HTTP extension.
97880 *
97881 * Does not support non-blocking.
97882 *
97883 * @access public
97884 * @since 2.7
97885 *
97886 * @param string $url
97887 * @param str|array $args Optional. Override the defaults.
97888 * @return array 'headers', 'body', and 'response' keys.
97889 */
97890 function request($url, $args = array()) {
97891 $defaults = array(
97892 'method' => 'GET', 'timeout' => 5,
97893 'redirection' => 5, 'httpversion' => '1.0',
97894 'blocking' => true,
97895 'headers' => array(), 'body' => null
97896 );
97897
97898 $r = wp_parse_args( $args, $defaults );
97899
97900 if ( isset($r['headers']['User-Agent']) ) {
97901 $r['user-agent'] = $r['headers']['User-Agent'];
97902 unset($r['headers']['User-Agent']);
97903 } else if( isset($r['headers']['user-agent']) ) {
97904 $r['user-agent'] = $r['headers']['user-agent'];
97905 unset($r['headers']['user-agent']);
97906 }
97907
97908 switch ( $r['method'] ) {
97909 case 'POST':
97910 $r['method'] = HTTP_METH_POST;
97911 break;
97912 case 'HEAD':
97913 $r['method'] = HTTP_METH_HEAD;
97914 break;
97915 case 'GET':
97916 default:
97917 $r['method'] = HTTP_METH_GET;
97918 }
97919
97920 $arrURL = parse_url($url);
97921
97922 if ( 'http' != $arrURL['scheme'] || 'https' != $arrURL['scheme'] )
97923 $url = str_replace($arrURL['scheme'], 'http', $url);
97924
97925 $options = array(
97926 'timeout' => $r['timeout'],
97927 'connecttimeout' => $r['timeout'],
97928 'redirect' => $r['redirection'],
97929 'useragent' => $r['user-agent'],
97930 'headers' => $r['headers'],
97931 );
97932
97933 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) ) //Emits warning level notices for max redirects and timeouts
97934 $strResponse = @http_request($r['method'], $url, $r['body'], $options, $info);
97935 else
97936 $strResponse = http_request($r['method'], $url, $r['body'], $options, $info); //Emits warning level notices for max redirects and timeouts
97937
97938 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
97939 return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']);
97940
97941 if ( ! $r['blocking'] )
97942 return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
97943
97944 list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
97945 $theHeaders = WP_Http::processHeaders($theHeaders);
97946
97947 if ( ! empty( $theBody ) && isset( $theHeaders['headers']['transfer-encoding'] ) && 'chunked' == $theHeaders['headers']['transfer-encoding'] ) {
97948 if ( !defined('WP_DEBUG') || ( defined('WP_DEBUG') && false === WP_DEBUG ) )
97949 $theBody = @http_chunked_decode($theBody);
97950 else
97951 $theBody = http_chunked_decode($theBody);
97952 }
97953
97954 $theResponse = array();
97955 $theResponse['code'] = $info['response_code'];
97956 $theResponse['message'] = get_status_header_desc($info['response_code']);
97957
97958 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse);
97959 }
97960
97961 /**
97962 * Whether this class can be used for retrieving an URL.
97963 *
97964 * @static
97965 * @since 2.7
97966 *
97967 * @return boolean False means this class can not be used, true means it can.
97968 */
97969 function test() {
97970 if ( function_exists('http_request') )
97971 return true;
97972
97973 return false;
97974 }
97975 }
97976
97977 /**
97978 * HTTP request method uses Curl extension to retrieve the url.
97979 *
97980 * Requires the Curl extension to be installed.
97981 *
97982 * @package WordPress
97983 * @subpackage HTTP
97984 * @since 2.7
97985 */
97986 class WP_Http_Curl {
97987 /**
97988 * Send a HTTP request to a URI using cURL extension.
97989 *
97990 * @access public
97991 * @since 2.7
97992 *
97993 * @param string $url
97994 * @param str|array $args Optional. Override the defaults.
97995 * @return array 'headers', 'body', and 'response' keys.
97996 */
97997 function request($url, $args = array()) {
97998 $defaults = array(
97999 'method' => 'GET', 'timeout' => 5,
98000 'redirection' => 5, 'httpversion' => '1.0',
98001 'blocking' => true,
98002 'headers' => array(), 'body' => null
98003 );
98004
98005 $r = wp_parse_args( $args, $defaults );
98006
98007 if ( isset($r['headers']['User-Agent']) ) {
98008 $r['user-agent'] = $r['headers']['User-Agent'];
98009 unset($r['headers']['User-Agent']);
98010 } else if( isset($r['headers']['user-agent']) ) {
98011 $r['user-agent'] = $r['headers']['user-agent'];
98012 unset($r['headers']['user-agent']);
98013 }
98014
98015 // If timeout is a float less than 1, round it up to 1.
98016 if ( $r['timeout'] > 0 && $r['timeout'] < 1 )
98017 $r['timeout'] = 1;
98018
98019 $handle = curl_init();
98020 curl_setopt( $handle, CURLOPT_URL, $url);
98021
98022 if ( 'HEAD' === $r['method'] ) {
98023 curl_setopt( $handle, CURLOPT_NOBODY, true );
98024 }
98025
98026 if ( true === $r['blocking'] ) {
98027 curl_setopt( $handle, CURLOPT_HEADER, true );
98028 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 1 );
98029 } else {
98030 curl_setopt( $handle, CURLOPT_HEADER, false );
98031 curl_setopt( $handle, CURLOPT_NOBODY, true );
98032 curl_setopt( $handle, CURLOPT_RETURNTRANSFER, 0 );
98033 }
98034
98035 curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
98036 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 1 );
98037 curl_setopt( $handle, CURLOPT_TIMEOUT, $r['timeout'] );
98038 curl_setopt( $handle, CURLOPT_MAXREDIRS, $r['redirection'] );
98039
98040 if ( !ini_get('safe_mode') && !ini_get('open_basedir') )
98041 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true );
98042
98043 if( ! is_null($r['headers']) )
98044 curl_setopt( $handle, CURLOPT_HTTPHEADER, $r['headers'] );
98045
98046 if ( $r['httpversion'] == '1.0' )
98047 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
98048 else
98049 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
98050
98051 if ( ! $r['blocking'] ) {
98052 curl_exec( $handle );
98053 curl_close( $handle );
98054 return array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') );
98055 }
98056
98057 $theResponse = curl_exec( $handle );
98058
98059 if ( !empty($theResponse) ) {
98060 $headerLength = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
98061 $theHeaders = trim( substr($theResponse, 0, $headerLength) );
98062 $theBody = substr( $theResponse, $headerLength );
98063 if ( false !== strrpos($theHeaders, "\r\n\r\n") ) {
98064 $headerParts = explode("\r\n\r\n", $theHeaders);
98065 $theHeaders = $headerParts[ count($headerParts) -1 ];
98066 }
98067 $theHeaders = WP_Http::processHeaders($theHeaders);
98068 } else {
98069 if ( $curl_error = curl_error($handle) )
98070 return new WP_Error('http_request_failed', $curl_error);
98071 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) )
98072 return new WP_Error('http_request_failed', __('Too many redirects.'));
98073
98074 $theHeaders = array( 'headers' => array() );
98075 $theBody = '';
98076 }
98077 $response = array();
98078 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
98079 $response['message'] = get_status_header_desc($response['code']);
98080
98081 curl_close( $handle );
98082
98083 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response);
98084 }
98085
98086 /**
98087 * Whether this class can be used for retrieving an URL.
98088 *
98089 * @static
98090 * @since 2.7
98091 *
98092 * @return boolean False means this class can not be used, true means it can.
98093 */
98094 function test() {
98095 if ( function_exists('curl_init') )
98096 return true;
98097
98098 return false;
98099 }
98100 }
98101
98102 /**
98103 * Returns the initialized WP_Http Object
98104 *
98105 * @since 2.7
98106 * @access private
98107 *
98108 * @return WP_Http HTTP Transport object.
98109 */
98110 function &_wp_http_get_object() {
98111 static $http;
98112
98113 if ( is_null($http) )
98114 $http = new WP_Http();
98115
98116 return $http;
98117 }
98118
98119 /**
98120 * Retrieve the raw response from the HTTP request.
98121 *
98122 * The array structure is a little complex.
98123 *
98124 * <code>
98125 * $res = array( 'headers' => array(), 'response' => array('code', 'message') );
98126 * </code>
98127 *
98128 * All of the headers in $res['headers'] are with the name as the key and the
98129 * value as the value. So to get the User-Agent, you would do the following.
98130 *
98131 * <code>
98132 * $user_agent = $res['headers']['user-agent'];
98133 * </code>
98134 *
98135 * The body is the raw response content and can be retrieved from $res['body'].
98136 *
98137 * This function is called first to make the request and there are other API
98138 * functions to abstract out the above convoluted setup.
98139 *
98140 * @since 2.7.0
98141 *
98142 * @param string $url Site URL to retrieve.
98143 * @param array $args Optional. Override the defaults.
98144 * @return string The body of the response
98145 */
98146 function wp_remote_request($url, $args = array()) {
98147 $objFetchSite = _wp_http_get_object();
98148 return $objFetchSite->request($url, $args);
98149 }
98150
98151 /**
98152 * Retrieve the raw response from the HTTP request using the GET method.
98153 *
98154 * @see wp_remote_request() For more information on the response array format.
98155 *
98156 * @since 2.7
98157 *
98158 * @param string $url Site URL to retrieve.
98159 * @param array $args Optional. Override the defaults.
98160 * @return string The body of the response
98161 */
98162 function wp_remote_get($url, $args = array()) {
98163 $objFetchSite = _wp_http_get_object();
98164
98165 return $objFetchSite->get($url, $args);
98166 }
98167
98168 /**
98169 * Retrieve the raw response from the HTTP request using the POST method.
98170 *
98171 * @see wp_remote_request() For more information on the response array format.
98172 *
98173 * @since 2.7
98174 *
98175 * @param string $url Site URL to retrieve.
98176 * @param array $args Optional. Override the defaults.
98177 * @return string The body of the response
98178 */
98179 function wp_remote_post($url, $args = array()) {
98180 $objFetchSite = _wp_http_get_object();
98181 return $objFetchSite->post($url, $args);
98182 }
98183
98184 /**
98185 * Retrieve the raw response from the HTTP request using the HEAD method.
98186 *
98187 * @see wp_remote_request() For more information on the response array format.
98188 *
98189 * @since 2.7
98190 *
98191 * @param string $url Site URL to retrieve.
98192 * @param array $args Optional. Override the defaults.
98193 * @return string The body of the response
98194 */
98195 function wp_remote_head($url, $args = array()) {
98196 $objFetchSite = _wp_http_get_object();
98197 return $objFetchSite->head($url, $args);
98198 }
98199
98200 /**
98201 * Retrieve only the headers from the raw response.
98202 *
98203 * @since 2.7
98204 *
98205 * @param array $response HTTP response.
98206 * @return array The headers of the response. Empty array if incorrect parameter given.
98207 */
98208 function wp_remote_retrieve_headers(&$response) {
98209 if ( ! isset($response['headers']) || ! is_array($response['headers']))
98210 return array();
98211
98212 return $response['headers'];
98213 }
98214
98215 /**
98216 * Retrieve a single header by name from the raw response.
98217 *
98218 * @since 2.7
98219 *
98220 * @param array $response
98221 * @param string $header Header name to retrieve value from.
98222 * @return array The header value. Empty string on if incorrect parameter given.
98223 */
98224 function wp_remote_retrieve_header(&$response, $header) {
98225 if ( ! isset($response['headers']) || ! is_array($response['headers']))
98226 return '';
98227
98228 if ( array_key_exists($header, $response['headers']) )
98229 return $response['headers'][$header];
98230
98231 return '';
98232 }
98233
98234 /**
98235 * Retrieve only the response code from the raw response.
98236 *
98237 * Will return an empty array if incorrect parameter value is given.
98238 *
98239 * @since 2.7
98240 *
98241 * @param array $response HTTP response.
98242 * @return array The keys 'code' and 'message' give information on the response.
98243 */
98244 function wp_remote_retrieve_response_code(&$response) {
98245 if ( ! isset($response['response']) || ! is_array($response['response']))
98246 return '';
98247
98248 return $response['response']['code'];
98249 }
98250
98251 /**
98252 * Retrieve only the response message from the raw response.
98253 *
98254 * Will return an empty array if incorrect parameter value is given.
98255 *
98256 * @since 2.7
98257 *
98258 * @param array $response HTTP response.
98259 * @return array The keys 'code' and 'message' give information on the response.
98260 */
98261 function wp_remote_retrieve_response_message(&$response) {
98262 if ( ! isset($response['response']) || ! is_array($response['response']))
98263 return '';
98264
98265 return $response['response']['message'];
98266 }
98267
98268 /**
98269 * Retrieve only the body from the raw response.
98270 *
98271 * @since 2.7
98272 *
98273 * @param array $response HTTP response.
98274 * @return string The body of the response. Empty string if no body or incorrect parameter given.
98275 */
98276 function wp_remote_retrieve_body(&$response) {
98277 if ( ! isset($response['body']) )
98278 return '';
98279
98280 return $response['body'];
98281 }
98282
98283 ?>