[ XREF Home ] [ Index ]

PHP Cross Reference of WordPress Trunk

Provided by Yoast

title

Body

[close]

/wp-includes/ -> class-http.php (source)

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


Generated: Wed Jun 1 08:30:02 2011 Cross-referenced by PHPXref 0.7
Provided by Yoast and awesome WordPress Hosting