[ Index ]

PHP Cross Reference of WordPress

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


Generated: Fri May 25 03:56:23 2012 Hosted by follow the white rabbit.