[ Index ] |
PHP Cross Reference of WordPress |
[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.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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Dec 3 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |