[ 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.7-3470169';
  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 = (0 === stripos($url, 'https://'));
 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' => Requests::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( Requests::$certificate_path ) ) {
 539              return Requests::$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          Requests::$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))) {
 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 = $url;
 638  
 639          if (!$options['filename']) {
 640              if (($pos = strpos($headers, "\r\n\r\n")) === false) {
 641                  // Crap!
 642                  throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
 643              }
 644  
 645              $headers = substr($return->raw, 0, $pos);
 646              $return->body = substr($return->raw, $pos + strlen("\n\r\n\r"));
 647          }
 648          else {
 649              $return->body = '';
 650          }
 651          // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
 652          $headers = str_replace("\r\n", "\n", $headers);
 653          // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
 654          $headers = preg_replace('/\n[ \t]/', ' ', $headers);
 655          $headers = explode("\n", $headers);
 656          preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
 657          if (empty($matches)) {
 658              throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);
 659          }
 660          $return->protocol_version = (float) $matches[1];
 661          $return->status_code = (int) $matches[2];
 662          if ($return->status_code >= 200 && $return->status_code < 300) {
 663              $return->success = true;
 664          }
 665  
 666          foreach ($headers as $header) {
 667              list($key, $value) = explode(':', $header, 2);
 668              $value = trim($value);
 669              preg_replace('#(\s+)#i', ' ', $value);
 670              $return->headers[$key] = $value;
 671          }
 672          if (isset($return->headers['transfer-encoding'])) {
 673              $return->body = self::decode_chunked($return->body);
 674              unset($return->headers['transfer-encoding']);
 675          }
 676          if (isset($return->headers['content-encoding'])) {
 677              $return->body = self::decompress($return->body);
 678          }
 679  
 680          //fsockopen and cURL compatibility
 681          if (isset($return->headers['connection'])) {
 682              unset($return->headers['connection']);
 683          }
 684  
 685          $options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options));
 686  
 687          if ($return->is_redirect() && $options['follow_redirects'] === true) {
 688              if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
 689                  if ($return->status_code === 303) {
 690                      $options['type'] = self::GET;
 691                  }
 692                  $options['redirected']++;
 693                  $location = $return->headers['location'];
 694                  if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {
 695                      // relative redirect, for compatibility make it absolute
 696                      $location = Requests_IRI::absolutize($url, $location);
 697                      $location = $location->uri;
 698                  }
 699  
 700                  $hook_args = array(
 701                      &$location,
 702                      &$req_headers,
 703                      &$req_data,
 704                      &$options,
 705                      $return
 706                  );
 707                  $options['hooks']->dispatch('requests.before_redirect', $hook_args);
 708                  $redirected = self::request($location, $req_headers, $req_data, $options['type'], $options);
 709                  $redirected->history[] = $return;
 710                  return $redirected;
 711              }
 712              elseif ($options['redirected'] >= $options['redirects']) {
 713                  throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);
 714              }
 715          }
 716  
 717          $return->redirects = $options['redirected'];
 718  
 719          $options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));
 720          return $return;
 721      }
 722  
 723      /**
 724       * Callback for `transport.internal.parse_response`
 725       *
 726       * Internal use only. Converts a raw HTTP response to a Requests_Response
 727       * while still executing a multiple request.
 728       *
 729       * @param string $response Full response text including headers and body (will be overwritten with Response instance)
 730       * @param array $request Request data as passed into {@see Requests::request_multiple()}
 731       * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
 732       */
 733  	public static function parse_multiple(&$response, $request) {
 734          try {
 735              $url = $request['url'];
 736              $headers = $request['headers'];
 737              $data = $request['data'];
 738              $options = $request['options'];
 739              $response = self::parse_response($response, $url, $headers, $data, $options);
 740          }
 741          catch (Requests_Exception $e) {
 742              $response = $e;
 743          }
 744      }
 745  
 746      /**
 747       * Decoded a chunked body as per RFC 2616
 748       *
 749       * @see https://tools.ietf.org/html/rfc2616#section-3.6.1
 750       * @param string $data Chunked body
 751       * @return string Decoded body
 752       */
 753  	protected static function decode_chunked($data) {
 754          if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {
 755              return $data;
 756          }
 757  
 758  
 759  
 760          $decoded = '';
 761          $encoded = $data;
 762  
 763          while (true) {
 764              $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);
 765              if (!$is_chunked) {
 766                  // Looks like it's not chunked after all
 767                  return $data;
 768              }
 769  
 770              $length = hexdec(trim($matches[1]));
 771              if ($length === 0) {
 772                  // Ignore trailer headers
 773                  return $decoded;
 774              }
 775  
 776              $chunk_length = strlen($matches[0]);
 777              $decoded .= substr($encoded, $chunk_length, $length);
 778              $encoded = substr($encoded, $chunk_length + $length + 2);
 779  
 780              if (trim($encoded) === '0' || empty($encoded)) {
 781                  return $decoded;
 782              }
 783          }
 784  
 785          // We'll never actually get down here
 786          // @codeCoverageIgnoreStart
 787      }
 788      // @codeCoverageIgnoreEnd
 789  
 790      /**
 791       * Convert a key => value array to a 'key: value' array for headers
 792       *
 793       * @param array $array Dictionary of header values
 794       * @return string[] List of headers
 795       */
 796  	public static function flatten($array) {
 797          $return = array();
 798          foreach ($array as $key => $value) {
 799              $return[] = sprintf('%s: %s', $key, $value);
 800          }
 801          return $return;
 802      }
 803  
 804      /**
 805       * Convert a key => value array to a 'key: value' array for headers
 806       *
 807       * @codeCoverageIgnore
 808       * @deprecated Misspelling of {@see Requests::flatten}
 809       * @param array $array Dictionary of header values
 810       * @return string[] List of headers
 811       */
 812  	public static function flattern($array) {
 813          return self::flatten($array);
 814      }
 815  
 816      /**
 817       * Decompress an encoded body
 818       *
 819       * Implements gzip, compress and deflate. Guesses which it is by attempting
 820       * to decode.
 821       *
 822       * @param string $data Compressed data in one of the above formats
 823       * @return string Decompressed string
 824       */
 825  	public static function decompress($data) {
 826          if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {
 827              // Not actually compressed. Probably cURL ruining this for us.
 828              return $data;
 829          }
 830  
 831          if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) {
 832              return $decoded;
 833          }
 834          elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {
 835              return $decoded;
 836          }
 837          elseif (($decoded = self::compatible_gzinflate($data)) !== false) {
 838              return $decoded;
 839          }
 840          elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) {
 841              return $decoded;
 842          }
 843  
 844          return $data;
 845      }
 846  
 847      /**
 848       * Decompression of deflated string while staying compatible with the majority of servers.
 849       *
 850       * Certain Servers will return deflated data with headers which PHP's gzinflate()
 851       * function cannot handle out of the box. The following function has been created from
 852       * various snippets on the gzinflate() PHP documentation.
 853       *
 854       * Warning: Magic numbers within. Due to the potential different formats that the compressed
 855       * data may be returned in, some "magic offsets" are needed to ensure proper decompression
 856       * takes place. For a simple progmatic way to determine the magic offset in use, see:
 857       * https://core.trac.wordpress.org/ticket/18273
 858       *
 859       * @since 2.8.1
 860       * @link https://core.trac.wordpress.org/ticket/18273
 861       * @link https://secure.php.net/manual/en/function.gzinflate.php#70875
 862       * @link https://secure.php.net/manual/en/function.gzinflate.php#77336
 863       *
 864       * @param string $gzData String to decompress.
 865       * @return string|bool False on failure.
 866       */
 867  	public static function compatible_gzinflate($gzData) {
 868          // Compressed data might contain a full zlib header, if so strip it for
 869          // gzinflate()
 870          if (substr($gzData, 0, 3) == "\x1f\x8b\x08") {
 871              $i = 10;
 872              $flg = ord(substr($gzData, 3, 1));
 873              if ($flg > 0) {
 874                  if ($flg & 4) {
 875                      list($xlen) = unpack('v', substr($gzData, $i, 2));
 876                      $i = $i + 2 + $xlen;
 877                  }
 878                  if ($flg & 8) {
 879                      $i = strpos($gzData, "\0", $i) + 1;
 880                  }
 881                  if ($flg & 16) {
 882                      $i = strpos($gzData, "\0", $i) + 1;
 883                  }
 884                  if ($flg & 2) {
 885                      $i = $i + 2;
 886                  }
 887              }
 888              $decompressed = self::compatible_gzinflate(substr($gzData, $i));
 889              if (false !== $decompressed) {
 890                  return $decompressed;
 891              }
 892          }
 893  
 894          // If the data is Huffman Encoded, we must first strip the leading 2
 895          // byte Huffman marker for gzinflate()
 896          // The response is Huffman coded by many compressors such as
 897          // java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's
 898          // System.IO.Compression.DeflateStream.
 899          //
 900          // See https://decompres.blogspot.com/ for a quick explanation of this
 901          // data type
 902          $huffman_encoded = false;
 903  
 904          // low nibble of first byte should be 0x08
 905          list(, $first_nibble)    = unpack('h', $gzData);
 906  
 907          // First 2 bytes should be divisible by 0x1F
 908          list(, $first_two_bytes) = unpack('n', $gzData);
 909  
 910          if (0x08 == $first_nibble && 0 == ($first_two_bytes % 0x1F)) {
 911              $huffman_encoded = true;
 912          }
 913  
 914          if ($huffman_encoded) {
 915              if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
 916                  return $decompressed;
 917              }
 918          }
 919  
 920          if ("\x50\x4b\x03\x04" == substr($gzData, 0, 4)) {
 921              // ZIP file format header
 922              // Offset 6: 2 bytes, General-purpose field
 923              // Offset 26: 2 bytes, filename length
 924              // Offset 28: 2 bytes, optional field length
 925              // Offset 30: Filename field, followed by optional field, followed
 926              // immediately by data
 927              list(, $general_purpose_flag) = unpack('v', substr($gzData, 6, 2));
 928  
 929              // If the file has been compressed on the fly, 0x08 bit is set of
 930              // the general purpose field. We can use this to differentiate
 931              // between a compressed document, and a ZIP file
 932              $zip_compressed_on_the_fly = (0x08 == (0x08 & $general_purpose_flag));
 933  
 934              if (!$zip_compressed_on_the_fly) {
 935                  // Don't attempt to decode a compressed zip file
 936                  return $gzData;
 937              }
 938  
 939              // Determine the first byte of data, based on the above ZIP header
 940              // offsets:
 941              $first_file_start = array_sum(unpack('v2', substr($gzData, 26, 4)));
 942              if (false !== ($decompressed = @gzinflate(substr($gzData, 30 + $first_file_start)))) {
 943                  return $decompressed;
 944              }
 945              return false;
 946          }
 947  
 948          // Finally fall back to straight gzinflate
 949          if (false !== ($decompressed = @gzinflate($gzData))) {
 950              return $decompressed;
 951          }
 952  
 953          // Fallback for all above failing, not expected, but included for
 954          // debugging and preventing regressions and to track stats
 955          if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
 956              return $decompressed;
 957          }
 958  
 959          return false;
 960      }
 961  
 962  	public static function match_domain($host, $reference) {
 963          // Check for a direct match
 964          if ($host === $reference) {
 965              return true;
 966          }
 967  
 968          // Calculate the valid wildcard match if the host is not an IP address
 969          // Also validates that the host has 3 parts or more, as per Firefox's
 970          // ruleset.
 971          $parts = explode('.', $host);
 972          if (ip2long($host) === false && count($parts) >= 3) {
 973              $parts[0] = '*';
 974              $wildcard = implode('.', $parts);
 975              if ($wildcard === $reference) {
 976                  return true;
 977              }
 978          }
 979  
 980          return false;
 981      }
 982  }


Generated: Sun Sep 27 01:00:03 2020 Cross-referenced by PHPXref 0.7.1