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