PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.20
Booking for Appointments and Events Calendar – Amelia v1.2.20
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / rmccue / requests / library / Requests.php
ameliabooking / vendor / rmccue / requests / library Last commit date
Requests 2 years ago Requests.php 2 years ago
Requests.php
1002 lines
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';
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 }
1002