[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * Requests for PHP
   4   *
   5   * Inspired by Requests for Python.
   6   *
   7   * Based on concepts from SimplePie_File, RequestCore and WP_Http.
   8   *
   9   * @package Requests
  10   */
  11  
  12  /**
  13   * Requests for PHP
  14   *
  15   * Inspired by Requests for Python.
  16   *
  17   * Based on concepts from SimplePie_File, RequestCore and WP_Http.
  18   *
  19   * @package Requests
  20   */
  21  class Requests {
  22      /**
  23       * POST method
  24       *
  25       * @var string
  26       */
  27      const POST = 'POST';
  28  
  29      /**
  30       * PUT method
  31       *
  32       * @var string
  33       */
  34      const PUT = 'PUT';
  35  
  36      /**
  37       * GET method
  38       *
  39       * @var string
  40       */
  41      const GET = 'GET';
  42  
  43      /**
  44       * HEAD method
  45       *
  46       * @var string
  47       */
  48      const HEAD = 'HEAD';
  49  
  50      /**
  51       * DELETE method
  52       *
  53       * @var string
  54       */
  55      const DELETE = 'DELETE';
  56  
  57      /**
  58       * OPTIONS method
  59       *
  60       * @var string
  61       */
  62      const OPTIONS = 'OPTIONS';
  63  
  64      /**
  65       * TRACE method
  66       *
  67       * @var string
  68       */
  69      const TRACE = 'TRACE';
  70  
  71      /**
  72       * PATCH method
  73       *
  74       * @link https://tools.ietf.org/html/rfc5789
  75       * @var string
  76       */
  77      const PATCH = 'PATCH';
  78  
  79      /**
  80       * Default size of buffer size to read streams
  81       *
  82       * @var integer
  83       */
  84      const BUFFER_SIZE = 1160;
  85  
  86      /**
  87       * Current version of Requests
  88       *
  89       * @var string
  90       */
  91      const VERSION = '1.8.1';
  92  
  93      /**
  94       * Registered transport classes
  95       *
  96       * @var array
  97       */
  98      protected static $transports = array();
  99  
 100      /**
 101       * Selected transport name
 102       *
 103       * Use {@see get_transport()} instead
 104       *
 105       * @var array
 106       */
 107      public static $transport = array();
 108  
 109      /**
 110       * Default certificate path.
 111       *
 112       * @see Requests::get_certificate_path()
 113       * @see Requests::set_certificate_path()
 114       *
 115       * @var string
 116       */
 117      protected static $certificate_path;
 118  
 119      /**
 120       * This is a static class, do not instantiate it
 121       *
 122       * @codeCoverageIgnore
 123       */
 124  	private function __construct() {}
 125  
 126      /**
 127       * Autoloader for Requests
 128       *
 129       * Register this with {@see register_autoloader()} if you'd like to avoid
 130       * having to create your own.
 131       *
 132       * (You can also use `spl_autoload_register` directly if you'd prefer.)
 133       *
 134       * @codeCoverageIgnore
 135       *
 136       * @param string $class Class name to load
 137       */
 138  	public static function autoloader($class) {
 139          // Check that the class starts with "Requests"
 140          if (strpos($class, 'Requests') !== 0) {
 141              return;
 142          }
 143  
 144          $file = str_replace('_', '/', $class);
 145          if (file_exists(dirname(__FILE__) . '/' . $file . '.php')) {
 146              require_once dirname(__FILE__) . '/' . $file . '.php';
 147          }
 148      }
 149  
 150      /**
 151       * Register the built-in autoloader
 152       *
 153       * @codeCoverageIgnore
 154       */
 155  	public static function register_autoloader() {
 156          spl_autoload_register(array('Requests', 'autoloader'));
 157      }
 158  
 159      /**
 160       * Register a transport
 161       *
 162       * @param string $transport Transport class to add, must support the Requests_Transport interface
 163       */
 164  	public static function add_transport($transport) {
 165          if (empty(self::$transports)) {
 166              self::$transports = array(
 167                  'Requests_Transport_cURL',
 168                  'Requests_Transport_fsockopen',
 169              );
 170          }
 171  
 172          self::$transports = array_merge(self::$transports, array($transport));
 173      }
 174  
 175      /**
 176       * Get a working transport
 177       *
 178       * @throws Requests_Exception If no valid transport is found (`notransport`)
 179       * @return Requests_Transport
 180       */
 181  	protected static function get_transport($capabilities = array()) {
 182          // Caching code, don't bother testing coverage
 183          // @codeCoverageIgnoreStart
 184          // array of capabilities as a string to be used as an array key
 185          ksort($capabilities);
 186          $cap_string = serialize($capabilities);
 187  
 188          // Don't search for a transport if it's already been done for these $capabilities
 189          if (isset(self::$transport[$cap_string]) && self::$transport[$cap_string] !== null) {
 190              $class = self::$transport[$cap_string];
 191              return new $class();
 192          }
 193          // @codeCoverageIgnoreEnd
 194  
 195          if (empty(self::$transports)) {
 196              self::$transports = array(
 197                  'Requests_Transport_cURL',
 198                  'Requests_Transport_fsockopen',
 199              );
 200          }
 201  
 202          // Find us a working transport
 203          foreach (self::$transports as $class) {
 204              if (!class_exists($class)) {
 205                  continue;
 206              }
 207  
 208              $result = call_user_func(array($class, 'test'), $capabilities);
 209              if ($result) {
 210                  self::$transport[$cap_string] = $class;
 211                  break;
 212              }
 213          }
 214          if (self::$transport[$cap_string] === null) {
 215              throw new Requests_Exception('No working transports found', 'notransport', self::$transports);
 216          }
 217  
 218          $class = self::$transport[$cap_string];
 219          return new $class();
 220      }
 221  
 222      /**#@+
 223       * @see request()
 224       * @param string $url
 225       * @param array $headers
 226       * @param array $options
 227       * @return Requests_Response
 228       */
 229      /**
 230       * Send a GET request
 231       */
 232  	public static function get($url, $headers = array(), $options = array()) {
 233          return self::request($url, $headers, null, self::GET, $options);
 234      }
 235  
 236      /**
 237       * Send a HEAD request
 238       */
 239  	public static function head($url, $headers = array(), $options = array()) {
 240          return self::request($url, $headers, null, self::HEAD, $options);
 241      }
 242  
 243      /**
 244       * Send a DELETE request
 245       */
 246  	public static function delete($url, $headers = array(), $options = array()) {
 247          return self::request($url, $headers, null, self::DELETE, $options);
 248      }
 249  
 250      /**
 251       * Send a TRACE request
 252       */
 253  	public static function trace($url, $headers = array(), $options = array()) {
 254          return self::request($url, $headers, null, self::TRACE, $options);
 255      }
 256      /**#@-*/
 257  
 258      /**#@+
 259       * @see request()
 260       * @param string $url
 261       * @param array $headers
 262       * @param array $data
 263       * @param array $options
 264       * @return Requests_Response
 265       */
 266      /**
 267       * Send a POST request
 268       */
 269  	public static function post($url, $headers = array(), $data = array(), $options = array()) {
 270          return self::request($url, $headers, $data, self::POST, $options);
 271      }
 272      /**
 273       * Send a PUT request
 274       */
 275  	public static function put($url, $headers = array(), $data = array(), $options = array()) {
 276          return self::request($url, $headers, $data, self::PUT, $options);
 277      }
 278  
 279      /**
 280       * Send an OPTIONS request
 281       */
 282  	public static function options($url, $headers = array(), $data = array(), $options = array()) {
 283          return self::request($url, $headers, $data, self::OPTIONS, $options);
 284      }
 285  
 286      /**
 287       * Send a PATCH request
 288       *
 289       * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
 290       * specification recommends that should send an ETag
 291       *
 292       * @link https://tools.ietf.org/html/rfc5789
 293       */
 294  	public static function patch($url, $headers, $data = array(), $options = array()) {
 295          return self::request($url, $headers, $data, self::PATCH, $options);
 296      }
 297      /**#@-*/
 298  
 299      /**
 300       * Main interface for HTTP requests
 301       *
 302       * This method initiates a request and sends it via a transport before
 303       * parsing.
 304       *
 305       * The `$options` parameter takes an associative array with the following
 306       * options:
 307       *
 308       * - `timeout`: How long should we wait for a response?
 309       *    Note: for cURL, a minimum of 1 second applies, as DNS resolution
 310       *    operates at second-resolution only.
 311       *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
 312       * - `connect_timeout`: How long should we wait while trying to connect?
 313       *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
 314       * - `useragent`: Useragent to send to the server
 315       *    (string, default: php-requests/$version)
 316       * - `follow_redirects`: Should we follow 3xx redirects?
 317       *    (boolean, default: true)
 318       * - `redirects`: How many times should we redirect before erroring?
 319       *    (integer, default: 10)
 320       * - `blocking`: Should we block processing on this request?
 321       *    (boolean, default: true)
 322       * - `filename`: File to stream the body to instead.
 323       *    (string|boolean, default: false)
 324       * - `auth`: Authentication handler or array of user/password details to use
 325       *    for Basic authentication
 326       *    (Requests_Auth|array|boolean, default: false)
 327       * - `proxy`: Proxy details to use for proxy by-passing and authentication
 328       *    (Requests_Proxy|array|string|boolean, default: false)
 329       * - `max_bytes`: Limit for the response body size.
 330       *    (integer|boolean, default: false)
 331       * - `idn`: Enable IDN parsing
 332       *    (boolean, default: true)
 333       * - `transport`: Custom transport. Either a class name, or a
 334       *    transport object. Defaults to the first working transport from
 335       *    {@see getTransport()}
 336       *    (string|Requests_Transport, default: {@see getTransport()})
 337       * - `hooks`: Hooks handler.
 338       *    (Requests_Hooker, default: new Requests_Hooks())
 339       * - `verify`: Should we verify SSL certificates? Allows passing in a custom
 340       *    certificate file as a string. (Using true uses the system-wide root
 341       *    certificate store instead, but this may have different behaviour
 342       *    across transports.)
 343       *    (string|boolean, default: library/Requests/Transport/cacert.pem)
 344       * - `verifyname`: Should we verify the common name in the SSL certificate?
 345       *    (boolean, default: true)
 346       * - `data_format`: How should we send the `$data` parameter?
 347       *    (string, one of 'query' or 'body', default: 'query' for
 348       *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
 349       *
 350       * @throws Requests_Exception On invalid URLs (`nonhttp`)
 351       *
 352       * @param string $url URL to request
 353       * @param array $headers Extra headers to send with the request
 354       * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
 355       * @param string $type HTTP request type (use Requests constants)
 356       * @param array $options Options for the request (see description for more information)
 357       * @return Requests_Response
 358       */
 359  	public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
 360          if (empty($options['type'])) {
 361              $options['type'] = $type;
 362          }
 363          $options = array_merge(self::get_default_options(), $options);
 364  
 365          self::set_defaults($url, $headers, $data, $type, $options);
 366  
 367          $options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
 368  
 369          if (!empty($options['transport'])) {
 370              $transport = $options['transport'];
 371  
 372              if (is_string($options['transport'])) {
 373                  $transport = new $transport();
 374              }
 375          }
 376          else {
 377              $need_ssl     = (stripos($url, 'https://') === 0);
 378              $capabilities = array('ssl' => $need_ssl);
 379              $transport    = self::get_transport($capabilities);
 380          }
 381          $response = $transport->request($url, $headers, $data, $options);
 382  
 383          $options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
 384  
 385          return self::parse_response($response, $url, $headers, $data, $options);
 386      }
 387  
 388      /**
 389       * Send multiple HTTP requests simultaneously
 390       *
 391       * The `$requests` parameter takes an associative or indexed array of
 392       * request fields. The key of each request can be used to match up the
 393       * request with the returned data, or with the request passed into your
 394       * `multiple.request.complete` callback.
 395       *
 396       * The request fields value is an associative array with the following keys:
 397       *
 398       * - `url`: Request URL Same as the `$url` parameter to
 399       *    {@see Requests::request}
 400       *    (string, required)
 401       * - `headers`: Associative array of header fields. Same as the `$headers`
 402       *    parameter to {@see Requests::request}
 403       *    (array, default: `array()`)
 404       * - `data`: Associative array of data fields or a string. Same as the
 405       *    `$data` parameter to {@see Requests::request}
 406       *    (array|string, default: `array()`)
 407       * - `type`: HTTP request type (use Requests constants). Same as the `$type`
 408       *    parameter to {@see Requests::request}
 409       *    (string, default: `Requests::GET`)
 410       * - `cookies`: Associative array of cookie name to value, or cookie jar.
 411       *    (array|Requests_Cookie_Jar)
 412       *
 413       * If the `$options` parameter is specified, individual requests will
 414       * inherit options from it. This can be used to use a single hooking system,
 415       * or set all the types to `Requests::POST`, for example.
 416       *
 417       * In addition, the `$options` parameter takes the following global options:
 418       *
 419       * - `complete`: A callback for when a request is complete. Takes two
 420       *    parameters, a Requests_Response/Requests_Exception reference, and the
 421       *    ID from the request array (Note: this can also be overridden on a
 422       *    per-request basis, although that's a little silly)
 423       *    (callback)
 424       *
 425       * @param array $requests Requests data (see description for more information)
 426       * @param array $options Global and default options (see {@see Requests::request})
 427       * @return array Responses (either Requests_Response or a Requests_Exception object)
 428       */
 429  	public static function request_multiple($requests, $options = array()) {
 430          $options = array_merge(self::get_default_options(true), $options);
 431  
 432          if (!empty($options['hooks'])) {
 433              $options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
 434              if (!empty($options['complete'])) {
 435                  $options['hooks']->register('multiple.request.complete', $options['complete']);
 436              }
 437          }
 438  
 439          foreach ($requests as $id => &$request) {
 440              if (!isset($request['headers'])) {
 441                  $request['headers'] = array();
 442              }
 443              if (!isset($request['data'])) {
 444                  $request['data'] = array();
 445              }
 446              if (!isset($request['type'])) {
 447                  $request['type'] = self::GET;
 448              }
 449              if (!isset($request['options'])) {
 450                  $request['options']         = $options;
 451                  $request['options']['type'] = $request['type'];
 452              }
 453              else {
 454                  if (empty($request['options']['type'])) {
 455                      $request['options']['type'] = $request['type'];
 456                  }
 457                  $request['options'] = array_merge($options, $request['options']);
 458              }
 459  
 460              self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
 461  
 462              // Ensure we only hook in once
 463              if ($request['options']['hooks'] !== $options['hooks']) {
 464                  $request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
 465                  if (!empty($request['options']['complete'])) {
 466                      $request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
 467                  }
 468              }
 469          }
 470          unset($request);
 471  
 472          if (!empty($options['transport'])) {
 473              $transport = $options['transport'];
 474  
 475              if (is_string($options['transport'])) {
 476                  $transport = new $transport();
 477              }
 478          }
 479          else {
 480              $transport = self::get_transport();
 481          }
 482          $responses = $transport->request_multiple($requests, $options);
 483  
 484          foreach ($responses as $id => &$response) {
 485              // If our hook got messed with somehow, ensure we end up with the
 486              // correct response
 487              if (is_string($response)) {
 488                  $request = $requests[$id];
 489                  self::parse_multiple($response, $request);
 490                  $request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
 491              }
 492          }
 493  
 494          return $responses;
 495      }
 496  
 497      /**
 498       * Get the default options
 499       *
 500       * @see Requests::request() for values returned by this method
 501       * @param boolean $multirequest Is this a multirequest?
 502       * @return array Default option values
 503       */
 504  	protected static function get_default_options($multirequest = false) {
 505          $defaults = array(
 506              'timeout'          => 10,
 507              'connect_timeout'  => 10,
 508              'useragent'        => 'php-requests/' . self::VERSION,
 509              'protocol_version' => 1.1,
 510              'redirected'       => 0,
 511              'redirects'        => 10,
 512              'follow_redirects' => true,
 513              'blocking'         => true,
 514              'type'             => self::GET,
 515              'filename'         => false,
 516              'auth'             => false,
 517              'proxy'            => false,
 518              'cookies'          => false,
 519              'max_bytes'        => false,
 520              'idn'              => true,
 521              'hooks'            => null,
 522              'transport'        => null,
 523              'verify'           => self::get_certificate_path(),
 524              'verifyname'       => true,
 525          );
 526          if ($multirequest !== false) {
 527              $defaults['complete'] = null;
 528          }
 529          return $defaults;
 530      }
 531  
 532      /**
 533       * Get default certificate path.
 534       *
 535       * @return string Default certificate path.
 536       */
 537  	public static function get_certificate_path() {
 538          if (!empty(self::$certificate_path)) {
 539              return self::$certificate_path;
 540          }
 541  
 542          return dirname(__FILE__) . '/Requests/Transport/cacert.pem';
 543      }
 544  
 545      /**
 546       * Set default certificate path.
 547       *
 548       * @param string $path Certificate path, pointing to a PEM file.
 549       */
 550  	public static function set_certificate_path($path) {
 551          self::$certificate_path = $path;
 552      }
 553  
 554      /**
 555       * Set the default values
 556       *
 557       * @param string $url URL to request
 558       * @param array $headers Extra headers to send with the request
 559       * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
 560       * @param string $type HTTP request type
 561       * @param array $options Options for the request
 562       * @return array $options
 563       */
 564  	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
 565          if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
 566              throw new Requests_Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
 567          }
 568  
 569          if (empty($options['hooks'])) {
 570              $options['hooks'] = new Requests_Hooks();
 571          }
 572  
 573          if (is_array($options['auth'])) {
 574              $options['auth'] = new Requests_Auth_Basic($options['auth']);
 575          }
 576          if ($options['auth'] !== false) {
 577              $options['auth']->register($options['hooks']);
 578          }
 579  
 580          if (is_string($options['proxy']) || is_array($options['proxy'])) {
 581              $options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);
 582          }
 583          if ($options['proxy'] !== false) {
 584              $options['proxy']->register($options['hooks']);
 585          }
 586  
 587          if (is_array($options['cookies'])) {
 588              $options['cookies'] = new Requests_Cookie_Jar($options['cookies']);
 589          }
 590          elseif (empty($options['cookies'])) {
 591              $options['cookies'] = new Requests_Cookie_Jar();
 592          }
 593          if ($options['cookies'] !== false) {
 594              $options['cookies']->register($options['hooks']);
 595          }
 596  
 597          if ($options['idn'] !== false) {
 598              $iri       = new Requests_IRI($url);
 599              $iri->host = Requests_IDNAEncoder::encode($iri->ihost);
 600              $url       = $iri->uri;
 601          }
 602  
 603          // Massage the type to ensure we support it.
 604          $type = strtoupper($type);
 605  
 606          if (!isset($options['data_format'])) {
 607              if (in_array($type, array(self::HEAD, self::GET, self::DELETE), true)) {
 608                  $options['data_format'] = 'query';
 609              }
 610              else {
 611                  $options['data_format'] = 'body';
 612              }
 613          }
 614      }
 615  
 616      /**
 617       * HTTP response parser
 618       *
 619       * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`)
 620       * @throws Requests_Exception On missing head/body separator (`noversion`)
 621       * @throws Requests_Exception On missing head/body separator (`toomanyredirects`)
 622       *
 623       * @param string $headers Full response text including headers and body
 624       * @param string $url Original request URL
 625       * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
 626       * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
 627       * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
 628       * @return Requests_Response
 629       */
 630  	protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
 631          $return = new Requests_Response();
 632          if (!$options['blocking']) {
 633              return $return;
 634          }
 635  
 636          $return->raw  = $headers;
 637          $return->url  = (string) $url;
 638          $return->body = '';
 639  
 640          if (!$options['filename']) {
 641              $pos = strpos($headers, "\r\n\r\n");
 642              if ($pos === false) {
 643                  // Crap!
 644                  throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
 645              }
 646  
 647              $headers = substr($return->raw, 0, $pos);
 648              // Headers will always be separated from the body by two new lines - `\n\r\n\r`.
 649              $body = substr($return->raw, $pos + 4);
 650              if (!empty($body)) {
 651                  $return->body = $body;
 652              }
 653          }
 654          // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
 655          $headers = str_replace("\r\n", "\n", $headers);
 656          // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
 657          $headers = preg_replace('/\n[ \t]/', ' ', $headers);
 658          $headers = explode("\n", $headers);
 659          preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
 660          if (empty($matches)) {
 661              throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);
 662          }
 663          $return->protocol_version = (float) $matches[1];
 664          $return->status_code      = (int) $matches[2];
 665          if ($return->status_code >= 200 && $return->status_code < 300) {
 666              $return->success = true;
 667          }
 668  
 669          foreach ($headers as $header) {
 670              list($key, $value) = explode(':', $header, 2);
 671              $value             = trim($value);
 672              preg_replace('#(\s+)#i', ' ', $value);
 673              $return->headers[$key] = $value;
 674          }
 675          if (isset($return->headers['transfer-encoding'])) {
 676              $return->body = self::decode_chunked($return->body);
 677              unset($return->headers['transfer-encoding']);
 678          }
 679          if (isset($return->headers['content-encoding'])) {
 680              $return->body = self::decompress($return->body);
 681          }
 682  
 683          //fsockopen and cURL compatibility
 684          if (isset($return->headers['connection'])) {
 685              unset($return->headers['connection']);
 686          }
 687  
 688          $options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options));
 689  
 690          if ($return->is_redirect() && $options['follow_redirects'] === true) {
 691              if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
 692                  if ($return->status_code === 303) {
 693                      $options['type'] = self::GET;
 694                  }
 695                  $options['redirected']++;
 696                  $location = $return->headers['location'];
 697                  if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {
 698                      // relative redirect, for compatibility make it absolute
 699                      $location = Requests_IRI::absolutize($url, $location);
 700                      $location = $location->uri;
 701                  }
 702  
 703                  $hook_args = array(
 704                      &$location,
 705                      &$req_headers,
 706                      &$req_data,
 707                      &$options,
 708                      $return,
 709                  );
 710                  $options['hooks']->dispatch('requests.before_redirect', $hook_args);
 711                  $redirected            = self::request($location, $req_headers, $req_data, $options['type'], $options);
 712                  $redirected->history[] = $return;
 713                  return $redirected;
 714              }
 715              elseif ($options['redirected'] >= $options['redirects']) {
 716                  throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);
 717              }
 718          }
 719  
 720          $return->redirects = $options['redirected'];
 721  
 722          $options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));
 723          return $return;
 724      }
 725  
 726      /**
 727       * Callback for `transport.internal.parse_response`
 728       *
 729       * Internal use only. Converts a raw HTTP response to a Requests_Response
 730       * while still executing a multiple request.
 731       *
 732       * @param string $response Full response text including headers and body (will be overwritten with Response instance)
 733       * @param array $request Request data as passed into {@see Requests::request_multiple()}
 734       * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
 735       */
 736  	public static function parse_multiple(&$response, $request) {
 737          try {
 738              $url      = $request['url'];
 739              $headers  = $request['headers'];
 740              $data     = $request['data'];
 741              $options  = $request['options'];
 742              $response = self::parse_response($response, $url, $headers, $data, $options);
 743          }
 744          catch (Requests_Exception $e) {
 745              $response = $e;
 746          }
 747      }
 748  
 749      /**
 750       * Decoded a chunked body as per RFC 2616
 751       *
 752       * @see https://tools.ietf.org/html/rfc2616#section-3.6.1
 753       * @param string $data Chunked body
 754       * @return string Decoded body
 755       */
 756  	protected static function decode_chunked($data) {
 757          if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {
 758              return $data;
 759          }
 760  
 761          $decoded = '';
 762          $encoded = $data;
 763  
 764          while (true) {
 765              $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);
 766              if (!$is_chunked) {
 767                  // Looks like it's not chunked after all
 768                  return $data;
 769              }
 770  
 771              $length = hexdec(trim($matches[1]));
 772              if ($length === 0) {
 773                  // Ignore trailer headers
 774                  return $decoded;
 775              }
 776  
 777              $chunk_length = strlen($matches[0]);
 778              $decoded     .= substr($encoded, $chunk_length, $length);
 779              $encoded      = substr($encoded, $chunk_length + $length + 2);
 780  
 781              if (trim($encoded) === '0' || empty($encoded)) {
 782                  return $decoded;
 783              }
 784          }
 785  
 786          // We'll never actually get down here
 787          // @codeCoverageIgnoreStart
 788      }
 789      // @codeCoverageIgnoreEnd
 790  
 791      /**
 792       * Convert a key => value array to a 'key: value' array for headers
 793       *
 794       * @param array $array Dictionary of header values
 795       * @return array List of headers
 796       */
 797  	public static function flatten($array) {
 798          $return = array();
 799          foreach ($array as $key => $value) {
 800              $return[] = sprintf('%s: %s', $key, $value);
 801          }
 802          return $return;
 803      }
 804  
 805      /**
 806       * Convert a key => value array to a 'key: value' array for headers
 807       *
 808       * @codeCoverageIgnore
 809       * @deprecated Misspelling of {@see Requests::flatten}
 810       * @param array $array Dictionary of header values
 811       * @return array List of headers
 812       */
 813  	public static function flattern($array) {
 814          return self::flatten($array);
 815      }
 816  
 817      /**
 818       * Decompress an encoded body
 819       *
 820       * Implements gzip, compress and deflate. Guesses which it is by attempting
 821       * to decode.
 822       *
 823       * @param string $data Compressed data in one of the above formats
 824       * @return string Decompressed string
 825       */
 826  	public static function decompress($data) {
 827          if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {
 828              // Not actually compressed. Probably cURL ruining this for us.
 829              return $data;
 830          }
 831  
 832          if (function_exists('gzdecode')) {
 833              // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.gzdecodeFound -- Wrapped in function_exists() for PHP 5.2.
 834              $decoded = @gzdecode($data);
 835              if ($decoded !== false) {
 836                  return $decoded;
 837              }
 838          }
 839  
 840          if (function_exists('gzinflate')) {
 841              $decoded = @gzinflate($data);
 842              if ($decoded !== false) {
 843                  return $decoded;
 844              }
 845          }
 846  
 847          $decoded = self::compatible_gzinflate($data);
 848          if ($decoded !== false) {
 849              return $decoded;
 850          }
 851  
 852          if (function_exists('gzuncompress')) {
 853              $decoded = @gzuncompress($data);
 854              if ($decoded !== false) {
 855                  return $decoded;
 856              }
 857          }
 858  
 859          return $data;
 860      }
 861  
 862      /**
 863       * Decompression of deflated string while staying compatible with the majority of servers.
 864       *
 865       * Certain Servers will return deflated data with headers which PHP's gzinflate()
 866       * function cannot handle out of the box. The following function has been created from
 867       * various snippets on the gzinflate() PHP documentation.
 868       *
 869       * Warning: Magic numbers within. Due to the potential different formats that the compressed
 870       * data may be returned in, some "magic offsets" are needed to ensure proper decompression
 871       * takes place. For a simple progmatic way to determine the magic offset in use, see:
 872       * https://core.trac.wordpress.org/ticket/18273
 873       *
 874       * @since 2.8.1
 875       * @link https://core.trac.wordpress.org/ticket/18273
 876       * @link https://secure.php.net/manual/en/function.gzinflate.php#70875
 877       * @link https://secure.php.net/manual/en/function.gzinflate.php#77336
 878       *
 879       * @param string $gz_data String to decompress.
 880       * @return string|bool False on failure.
 881       */
 882  	public static function compatible_gzinflate($gz_data) {
 883          // Compressed data might contain a full zlib header, if so strip it for
 884          // gzinflate()
 885          if (substr($gz_data, 0, 3) === "\x1f\x8b\x08") {
 886              $i   = 10;
 887              $flg = ord(substr($gz_data, 3, 1));
 888              if ($flg > 0) {
 889                  if ($flg & 4) {
 890                      list($xlen) = unpack('v', substr($gz_data, $i, 2));
 891                      $i         += 2 + $xlen;
 892                  }
 893                  if ($flg & 8) {
 894                      $i = strpos($gz_data, "\0", $i) + 1;
 895                  }
 896                  if ($flg & 16) {
 897                      $i = strpos($gz_data, "\0", $i) + 1;
 898                  }
 899                  if ($flg & 2) {
 900                      $i += 2;
 901                  }
 902              }
 903              $decompressed = self::compatible_gzinflate(substr($gz_data, $i));
 904              if ($decompressed !== false) {
 905                  return $decompressed;
 906              }
 907          }
 908  
 909          // If the data is Huffman Encoded, we must first strip the leading 2
 910          // byte Huffman marker for gzinflate()
 911          // The response is Huffman coded by many compressors such as
 912          // java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's
 913          // System.IO.Compression.DeflateStream.
 914          //
 915          // See https://decompres.blogspot.com/ for a quick explanation of this
 916          // data type
 917          $huffman_encoded = false;
 918  
 919          // low nibble of first byte should be 0x08
 920          list(, $first_nibble) = unpack('h', $gz_data);
 921  
 922          // First 2 bytes should be divisible by 0x1F
 923          list(, $first_two_bytes) = unpack('n', $gz_data);
 924  
 925          if ($first_nibble === 0x08 && ($first_two_bytes % 0x1F) === 0) {
 926              $huffman_encoded = true;
 927          }
 928  
 929          if ($huffman_encoded) {
 930              $decompressed = @gzinflate(substr($gz_data, 2));
 931              if ($decompressed !== false) {
 932                  return $decompressed;
 933              }
 934          }
 935  
 936          if (substr($gz_data, 0, 4) === "\x50\x4b\x03\x04") {
 937              // ZIP file format header
 938              // Offset 6: 2 bytes, General-purpose field
 939              // Offset 26: 2 bytes, filename length
 940              // Offset 28: 2 bytes, optional field length
 941              // Offset 30: Filename field, followed by optional field, followed
 942              // immediately by data
 943              list(, $general_purpose_flag) = unpack('v', substr($gz_data, 6, 2));
 944  
 945              // If the file has been compressed on the fly, 0x08 bit is set of
 946              // the general purpose field. We can use this to differentiate
 947              // between a compressed document, and a ZIP file
 948              $zip_compressed_on_the_fly = ((0x08 & $general_purpose_flag) === 0x08);
 949  
 950              if (!$zip_compressed_on_the_fly) {
 951                  // Don't attempt to decode a compressed zip file
 952                  return $gz_data;
 953              }
 954  
 955              // Determine the first byte of data, based on the above ZIP header
 956              // offsets:
 957              $first_file_start = array_sum(unpack('v2', substr($gz_data, 26, 4)));
 958              $decompressed     = @gzinflate(substr($gz_data, 30 + $first_file_start));
 959              if ($decompressed !== false) {
 960                  return $decompressed;
 961              }
 962              return false;
 963          }
 964  
 965          // Finally fall back to straight gzinflate
 966          $decompressed = @gzinflate($gz_data);
 967          if ($decompressed !== false) {
 968              return $decompressed;
 969          }
 970  
 971          // Fallback for all above failing, not expected, but included for
 972          // debugging and preventing regressions and to track stats
 973          $decompressed = @gzinflate(substr($gz_data, 2));
 974          if ($decompressed !== false) {
 975              return $decompressed;
 976          }
 977  
 978          return false;
 979      }
 980  
 981  	public static function match_domain($host, $reference) {
 982          // Check for a direct match
 983          if ($host === $reference) {
 984              return true;
 985          }
 986  
 987          // Calculate the valid wildcard match if the host is not an IP address
 988          // Also validates that the host has 3 parts or more, as per Firefox's
 989          // ruleset.
 990          $parts = explode('.', $host);
 991          if (ip2long($host) === false && count($parts) >= 3) {
 992              $parts[0] = '*';
 993              $wildcard = implode('.', $parts);
 994              if ($wildcard === $reference) {
 995                  return true;
 996              }
 997          }
 998  
 999          return false;
1000      }
1001  }


Generated: Fri Apr 26 01:00:03 2024 Cross-referenced by PHPXref 0.7.1