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