]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/HTTP/Request2/Response.php
Merge remote-tracking branch 'statusnet/master'
[quix0rs-gnu-social.git] / extlib / HTTP / Request2 / Response.php
1 <?php\r
2 /**\r
3  * Class representing a HTTP response\r
4  *\r
5  * PHP version 5\r
6  *\r
7  * LICENSE:\r
8  *\r
9  * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>\r
10  * All rights reserved.\r
11  *\r
12  * Redistribution and use in source and binary forms, with or without\r
13  * modification, are permitted provided that the following conditions\r
14  * are met:\r
15  *\r
16  *    * Redistributions of source code must retain the above copyright\r
17  *      notice, this list of conditions and the following disclaimer.\r
18  *    * Redistributions in binary form must reproduce the above copyright\r
19  *      notice, this list of conditions and the following disclaimer in the\r
20  *      documentation and/or other materials provided with the distribution.\r
21  *    * The names of the authors may not be used to endorse or promote products\r
22  *      derived from this software without specific prior written permission.\r
23  *\r
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
26  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
27  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
32  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
35  *\r
36  * @category   HTTP\r
37  * @package    HTTP_Request2\r
38  * @author     Alexey Borzov <avb@php.net>\r
39  * @license    http://opensource.org/licenses/bsd-license.php New BSD License\r
40  * @version    SVN: $Id: Response.php 309921 2011-04-03 16:43:02Z avb $\r
41  * @link       http://pear.php.net/package/HTTP_Request2\r
42  */\r
43 \r
44 /**\r
45  * Exception class for HTTP_Request2 package\r
46  */\r
47 require_once 'HTTP/Request2/Exception.php';\r
48 \r
49 /**\r
50  * Class representing a HTTP response\r
51  *\r
52  * The class is designed to be used in "streaming" scenario, building the\r
53  * response as it is being received:\r
54  * <code>\r
55  * $statusLine = read_status_line();\r
56  * $response = new HTTP_Request2_Response($statusLine);\r
57  * do {\r
58  *     $headerLine = read_header_line();\r
59  *     $response->parseHeaderLine($headerLine);\r
60  * } while ($headerLine != '');\r
61  *\r
62  * while ($chunk = read_body()) {\r
63  *     $response->appendBody($chunk);\r
64  * }\r
65  *\r
66  * var_dump($response->getHeader(), $response->getCookies(), $response->getBody());\r
67  * </code>\r
68  *\r
69  *\r
70  * @category   HTTP\r
71  * @package    HTTP_Request2\r
72  * @author     Alexey Borzov <avb@php.net>\r
73  * @version    Release: 2.0.0RC1\r
74  * @link       http://tools.ietf.org/html/rfc2616#section-6\r
75  */\r
76 class HTTP_Request2_Response\r
77 {\r
78    /**\r
79     * HTTP protocol version (e.g. 1.0, 1.1)\r
80     * @var  string\r
81     */\r
82     protected $version;\r
83 \r
84    /**\r
85     * Status code\r
86     * @var  integer\r
87     * @link http://tools.ietf.org/html/rfc2616#section-6.1.1\r
88     */\r
89     protected $code;\r
90 \r
91    /**\r
92     * Reason phrase\r
93     * @var  string\r
94     * @link http://tools.ietf.org/html/rfc2616#section-6.1.1\r
95     */\r
96     protected $reasonPhrase;\r
97 \r
98    /**\r
99     * Effective URL (may be different from original request URL in case of redirects)\r
100     * @var  string\r
101     */\r
102     protected $effectiveUrl;\r
103 \r
104    /**\r
105     * Associative array of response headers\r
106     * @var  array\r
107     */\r
108     protected $headers = array();\r
109 \r
110    /**\r
111     * Cookies set in the response\r
112     * @var  array\r
113     */\r
114     protected $cookies = array();\r
115 \r
116    /**\r
117     * Name of last header processed by parseHederLine()\r
118     *\r
119     * Used to handle the headers that span multiple lines\r
120     *\r
121     * @var  string\r
122     */\r
123     protected $lastHeader = null;\r
124 \r
125    /**\r
126     * Response body\r
127     * @var  string\r
128     */\r
129     protected $body = '';\r
130 \r
131    /**\r
132     * Whether the body is still encoded by Content-Encoding\r
133     *\r
134     * cURL provides the decoded body to the callback; if we are reading from\r
135     * socket the body is still gzipped / deflated\r
136     *\r
137     * @var  bool\r
138     */\r
139     protected $bodyEncoded;\r
140 \r
141    /**\r
142     * Associative array of HTTP status code / reason phrase.\r
143     *\r
144     * @var  array\r
145     * @link http://tools.ietf.org/html/rfc2616#section-10\r
146     */\r
147     protected static $phrases = array(\r
148 \r
149         // 1xx: Informational - Request received, continuing process\r
150         100 => 'Continue',\r
151         101 => 'Switching Protocols',\r
152 \r
153         // 2xx: Success - The action was successfully received, understood and\r
154         // accepted\r
155         200 => 'OK',\r
156         201 => 'Created',\r
157         202 => 'Accepted',\r
158         203 => 'Non-Authoritative Information',\r
159         204 => 'No Content',\r
160         205 => 'Reset Content',\r
161         206 => 'Partial Content',\r
162 \r
163         // 3xx: Redirection - Further action must be taken in order to complete\r
164         // the request\r
165         300 => 'Multiple Choices',\r
166         301 => 'Moved Permanently',\r
167         302 => 'Found',  // 1.1\r
168         303 => 'See Other',\r
169         304 => 'Not Modified',\r
170         305 => 'Use Proxy',\r
171         307 => 'Temporary Redirect',\r
172 \r
173         // 4xx: Client Error - The request contains bad syntax or cannot be\r
174         // fulfilled\r
175         400 => 'Bad Request',\r
176         401 => 'Unauthorized',\r
177         402 => 'Payment Required',\r
178         403 => 'Forbidden',\r
179         404 => 'Not Found',\r
180         405 => 'Method Not Allowed',\r
181         406 => 'Not Acceptable',\r
182         407 => 'Proxy Authentication Required',\r
183         408 => 'Request Timeout',\r
184         409 => 'Conflict',\r
185         410 => 'Gone',\r
186         411 => 'Length Required',\r
187         412 => 'Precondition Failed',\r
188         413 => 'Request Entity Too Large',\r
189         414 => 'Request-URI Too Long',\r
190         415 => 'Unsupported Media Type',\r
191         416 => 'Requested Range Not Satisfiable',\r
192         417 => 'Expectation Failed',\r
193 \r
194         // 5xx: Server Error - The server failed to fulfill an apparently\r
195         // valid request\r
196         500 => 'Internal Server Error',\r
197         501 => 'Not Implemented',\r
198         502 => 'Bad Gateway',\r
199         503 => 'Service Unavailable',\r
200         504 => 'Gateway Timeout',\r
201         505 => 'HTTP Version Not Supported',\r
202         509 => 'Bandwidth Limit Exceeded',\r
203 \r
204     );\r
205 \r
206    /**\r
207     * Constructor, parses the response status line\r
208     *\r
209     * @param    string Response status line (e.g. "HTTP/1.1 200 OK")\r
210     * @param    bool   Whether body is still encoded by Content-Encoding\r
211     * @param    string Effective URL of the response\r
212     * @throws   HTTP_Request2_MessageException if status line is invalid according to spec\r
213     */\r
214     public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null)\r
215     {\r
216         if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {\r
217             throw new HTTP_Request2_MessageException(\r
218                 "Malformed response: {$statusLine}",\r
219                 HTTP_Request2_Exception::MALFORMED_RESPONSE\r
220             );\r
221         }\r
222         $this->version = $m[1];\r
223         $this->code    = intval($m[2]);\r
224         if (!empty($m[3])) {\r
225             $this->reasonPhrase = trim($m[3]);\r
226         } elseif (!empty(self::$phrases[$this->code])) {\r
227             $this->reasonPhrase = self::$phrases[$this->code];\r
228         }\r
229         $this->bodyEncoded  = (bool)$bodyEncoded;\r
230         $this->effectiveUrl = (string)$effectiveUrl;\r
231     }\r
232 \r
233    /**\r
234     * Parses the line from HTTP response filling $headers array\r
235     *\r
236     * The method should be called after reading the line from socket or receiving\r
237     * it into cURL callback. Passing an empty string here indicates the end of\r
238     * response headers and triggers additional processing, so be sure to pass an\r
239     * empty string in the end.\r
240     *\r
241     * @param    string  Line from HTTP response\r
242     */\r
243     public function parseHeaderLine($headerLine)\r
244     {\r
245         $headerLine = trim($headerLine, "\r\n");\r
246 \r
247         // empty string signals the end of headers, process the received ones\r
248         if ('' == $headerLine) {\r
249             if (!empty($this->headers['set-cookie'])) {\r
250                 $cookies = is_array($this->headers['set-cookie'])?\r
251                            $this->headers['set-cookie']:\r
252                            array($this->headers['set-cookie']);\r
253                 foreach ($cookies as $cookieString) {\r
254                     $this->parseCookie($cookieString);\r
255                 }\r
256                 unset($this->headers['set-cookie']);\r
257             }\r
258             foreach (array_keys($this->headers) as $k) {\r
259                 if (is_array($this->headers[$k])) {\r
260                     $this->headers[$k] = implode(', ', $this->headers[$k]);\r
261                 }\r
262             }\r
263 \r
264         // string of the form header-name: header value\r
265         } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) {\r
266             $name  = strtolower($m[1]);\r
267             $value = trim($m[2]);\r
268             if (empty($this->headers[$name])) {\r
269                 $this->headers[$name] = $value;\r
270             } else {\r
271                 if (!is_array($this->headers[$name])) {\r
272                     $this->headers[$name] = array($this->headers[$name]);\r
273                 }\r
274                 $this->headers[$name][] = $value;\r
275             }\r
276             $this->lastHeader = $name;\r
277 \r
278         // continuation of a previous header\r
279         } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {\r
280             if (!is_array($this->headers[$this->lastHeader])) {\r
281                 $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);\r
282             } else {\r
283                 $key = count($this->headers[$this->lastHeader]) - 1;\r
284                 $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);\r
285             }\r
286         }\r
287     }\r
288 \r
289    /**\r
290     * Parses a Set-Cookie header to fill $cookies array\r
291     *\r
292     * @param    string    value of Set-Cookie header\r
293     * @link     http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html\r
294     */\r
295     protected function parseCookie($cookieString)\r
296     {\r
297         $cookie = array(\r
298             'expires' => null,\r
299             'domain'  => null,\r
300             'path'    => null,\r
301             'secure'  => false\r
302         );\r
303 \r
304         // Only a name=value pair\r
305         if (!strpos($cookieString, ';')) {\r
306             $pos = strpos($cookieString, '=');\r
307             $cookie['name']  = trim(substr($cookieString, 0, $pos));\r
308             $cookie['value'] = trim(substr($cookieString, $pos + 1));\r
309 \r
310         // Some optional parameters are supplied\r
311         } else {\r
312             $elements = explode(';', $cookieString);\r
313             $pos = strpos($elements[0], '=');\r
314             $cookie['name']  = trim(substr($elements[0], 0, $pos));\r
315             $cookie['value'] = trim(substr($elements[0], $pos + 1));\r
316 \r
317             for ($i = 1; $i < count($elements); $i++) {\r
318                 if (false === strpos($elements[$i], '=')) {\r
319                     $elName  = trim($elements[$i]);\r
320                     $elValue = null;\r
321                 } else {\r
322                     list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));\r
323                 }\r
324                 $elName = strtolower($elName);\r
325                 if ('secure' == $elName) {\r
326                     $cookie['secure'] = true;\r
327                 } elseif ('expires' == $elName) {\r
328                     $cookie['expires'] = str_replace('"', '', $elValue);\r
329                 } elseif ('path' == $elName || 'domain' == $elName) {\r
330                     $cookie[$elName] = urldecode($elValue);\r
331                 } else {\r
332                     $cookie[$elName] = $elValue;\r
333                 }\r
334             }\r
335         }\r
336         $this->cookies[] = $cookie;\r
337     }\r
338 \r
339    /**\r
340     * Appends a string to the response body\r
341     * @param    string\r
342     */\r
343     public function appendBody($bodyChunk)\r
344     {\r
345         $this->body .= $bodyChunk;\r
346     }\r
347 \r
348    /**\r
349     * Returns the effective URL of the response\r
350     *\r
351     * This may be different from the request URL if redirects were followed.\r
352     *\r
353     * @return string\r
354     * @link   http://pear.php.net/bugs/bug.php?id=18412\r
355     */\r
356     public function getEffectiveUrl()\r
357     {\r
358         return $this->effectiveUrl;\r
359     }\r
360 \r
361    /**\r
362     * Returns the status code\r
363     * @return   integer\r
364     */\r
365     public function getStatus()\r
366     {\r
367         return $this->code;\r
368     }\r
369 \r
370    /**\r
371     * Returns the reason phrase\r
372     * @return   string\r
373     */\r
374     public function getReasonPhrase()\r
375     {\r
376         return $this->reasonPhrase;\r
377     }\r
378 \r
379    /**\r
380     * Whether response is a redirect that can be automatically handled by HTTP_Request2\r
381     * @return   bool\r
382     */\r
383     public function isRedirect()\r
384     {\r
385         return in_array($this->code, array(300, 301, 302, 303, 307))\r
386                && isset($this->headers['location']);\r
387     }\r
388 \r
389    /**\r
390     * Returns either the named header or all response headers\r
391     *\r
392     * @param    string          Name of header to return\r
393     * @return   string|array    Value of $headerName header (null if header is\r
394     *                           not present), array of all response headers if\r
395     *                           $headerName is null\r
396     */\r
397     public function getHeader($headerName = null)\r
398     {\r
399         if (null === $headerName) {\r
400             return $this->headers;\r
401         } else {\r
402             $headerName = strtolower($headerName);\r
403             return isset($this->headers[$headerName])? $this->headers[$headerName]: null;\r
404         }\r
405     }\r
406 \r
407    /**\r
408     * Returns cookies set in response\r
409     *\r
410     * @return   array\r
411     */\r
412     public function getCookies()\r
413     {\r
414         return $this->cookies;\r
415     }\r
416 \r
417    /**\r
418     * Returns the body of the response\r
419     *\r
420     * @return   string\r
421     * @throws   HTTP_Request2_Exception if body cannot be decoded\r
422     */\r
423     public function getBody()\r
424     {\r
425         if (0 == strlen($this->body) || !$this->bodyEncoded ||\r
426             !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))\r
427         ) {\r
428             return $this->body;\r
429 \r
430         } else {\r
431             if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {\r
432                 $oldEncoding = mb_internal_encoding();\r
433                 mb_internal_encoding('iso-8859-1');\r
434             }\r
435 \r
436             try {\r
437                 switch (strtolower($this->getHeader('content-encoding'))) {\r
438                     case 'gzip':\r
439                         $decoded = self::decodeGzip($this->body);\r
440                         break;\r
441                     case 'deflate':\r
442                         $decoded = self::decodeDeflate($this->body);\r
443                 }\r
444             } catch (Exception $e) {\r
445             }\r
446 \r
447             if (!empty($oldEncoding)) {\r
448                 mb_internal_encoding($oldEncoding);\r
449             }\r
450             if (!empty($e)) {\r
451                 throw $e;\r
452             }\r
453             return $decoded;\r
454         }\r
455     }\r
456 \r
457    /**\r
458     * Get the HTTP version of the response\r
459     *\r
460     * @return   string\r
461     */\r
462     public function getVersion()\r
463     {\r
464         return $this->version;\r
465     }\r
466 \r
467    /**\r
468     * Decodes the message-body encoded by gzip\r
469     *\r
470     * The real decoding work is done by gzinflate() built-in function, this\r
471     * method only parses the header and checks data for compliance with\r
472     * RFC 1952\r
473     *\r
474     * @param    string  gzip-encoded data\r
475     * @return   string  decoded data\r
476     * @throws   HTTP_Request2_LogicException\r
477     * @throws   HTTP_Request2_MessageException\r
478     * @link     http://tools.ietf.org/html/rfc1952\r
479     */\r
480     public static function decodeGzip($data)\r
481     {\r
482         $length = strlen($data);\r
483         // If it doesn't look like gzip-encoded data, don't bother\r
484         if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {\r
485             return $data;\r
486         }\r
487         if (!function_exists('gzinflate')) {\r
488             throw new HTTP_Request2_LogicException(\r
489                 'Unable to decode body: gzip extension not available',\r
490                 HTTP_Request2_Exception::MISCONFIGURATION\r
491             );\r
492         }\r
493         $method = ord(substr($data, 2, 1));\r
494         if (8 != $method) {\r
495             throw new HTTP_Request2_MessageException(\r
496                 'Error parsing gzip header: unknown compression method',\r
497                 HTTP_Request2_Exception::DECODE_ERROR\r
498             );\r
499         }\r
500         $flags = ord(substr($data, 3, 1));\r
501         if ($flags & 224) {\r
502             throw new HTTP_Request2_MessageException(\r
503                 'Error parsing gzip header: reserved bits are set',\r
504                 HTTP_Request2_Exception::DECODE_ERROR\r
505             );\r
506         }\r
507 \r
508         // header is 10 bytes minimum. may be longer, though.\r
509         $headerLength = 10;\r
510         // extra fields, need to skip 'em\r
511         if ($flags & 4) {\r
512             if ($length - $headerLength - 2 < 8) {\r
513                 throw new HTTP_Request2_MessageException(\r
514                     'Error parsing gzip header: data too short',\r
515                     HTTP_Request2_Exception::DECODE_ERROR\r
516                 );\r
517             }\r
518             $extraLength = unpack('v', substr($data, 10, 2));\r
519             if ($length - $headerLength - 2 - $extraLength[1] < 8) {\r
520                 throw new HTTP_Request2_MessageException(\r
521                     'Error parsing gzip header: data too short',\r
522                     HTTP_Request2_Exception::DECODE_ERROR\r
523                 );\r
524             }\r
525             $headerLength += $extraLength[1] + 2;\r
526         }\r
527         // file name, need to skip that\r
528         if ($flags & 8) {\r
529             if ($length - $headerLength - 1 < 8) {\r
530                 throw new HTTP_Request2_MessageException(\r
531                     'Error parsing gzip header: data too short',\r
532                     HTTP_Request2_Exception::DECODE_ERROR\r
533                 );\r
534             }\r
535             $filenameLength = strpos(substr($data, $headerLength), chr(0));\r
536             if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {\r
537                 throw new HTTP_Request2_MessageException(\r
538                     'Error parsing gzip header: data too short',\r
539                     HTTP_Request2_Exception::DECODE_ERROR\r
540                 );\r
541             }\r
542             $headerLength += $filenameLength + 1;\r
543         }\r
544         // comment, need to skip that also\r
545         if ($flags & 16) {\r
546             if ($length - $headerLength - 1 < 8) {\r
547                 throw new HTTP_Request2_MessageException(\r
548                     'Error parsing gzip header: data too short',\r
549                     HTTP_Request2_Exception::DECODE_ERROR\r
550                 );\r
551             }\r
552             $commentLength = strpos(substr($data, $headerLength), chr(0));\r
553             if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {\r
554                 throw new HTTP_Request2_MessageException(\r
555                     'Error parsing gzip header: data too short',\r
556                     HTTP_Request2_Exception::DECODE_ERROR\r
557                 );\r
558             }\r
559             $headerLength += $commentLength + 1;\r
560         }\r
561         // have a CRC for header. let's check\r
562         if ($flags & 2) {\r
563             if ($length - $headerLength - 2 < 8) {\r
564                 throw new HTTP_Request2_MessageException(\r
565                     'Error parsing gzip header: data too short',\r
566                     HTTP_Request2_Exception::DECODE_ERROR\r
567                 );\r
568             }\r
569             $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));\r
570             $crcStored = unpack('v', substr($data, $headerLength, 2));\r
571             if ($crcReal != $crcStored[1]) {\r
572                 throw new HTTP_Request2_MessageException(\r
573                     'Header CRC check failed',\r
574                     HTTP_Request2_Exception::DECODE_ERROR\r
575                 );\r
576             }\r
577             $headerLength += 2;\r
578         }\r
579         // unpacked data CRC and size at the end of encoded data\r
580         $tmp = unpack('V2', substr($data, -8));\r
581         $dataCrc  = $tmp[1];\r
582         $dataSize = $tmp[2];\r
583 \r
584         // finally, call the gzinflate() function\r
585         // don't pass $dataSize to gzinflate, see bugs #13135, #14370\r
586         $unpacked = gzinflate(substr($data, $headerLength, -8));\r
587         if (false === $unpacked) {\r
588             throw new HTTP_Request2_MessageException(\r
589                 'gzinflate() call failed',\r
590                 HTTP_Request2_Exception::DECODE_ERROR\r
591             );\r
592         } elseif ($dataSize != strlen($unpacked)) {\r
593             throw new HTTP_Request2_MessageException(\r
594                 'Data size check failed',\r
595                 HTTP_Request2_Exception::DECODE_ERROR\r
596             );\r
597         } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {\r
598             throw new HTTP_Request2_Exception(\r
599                 'Data CRC check failed',\r
600                 HTTP_Request2_Exception::DECODE_ERROR\r
601             );\r
602         }\r
603         return $unpacked;\r
604     }\r
605 \r
606    /**\r
607     * Decodes the message-body encoded by deflate\r
608     *\r
609     * @param    string  deflate-encoded data\r
610     * @return   string  decoded data\r
611     * @throws   HTTP_Request2_LogicException\r
612     */\r
613     public static function decodeDeflate($data)\r
614     {\r
615         if (!function_exists('gzuncompress')) {\r
616             throw new HTTP_Request2_LogicException(\r
617                 'Unable to decode body: gzip extension not available',\r
618                 HTTP_Request2_Exception::MISCONFIGURATION\r
619             );\r
620         }\r
621         // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,\r
622         // while many applications send raw deflate stream from RFC 1951.\r
623         // We should check for presence of zlib header and use gzuncompress() or\r
624         // gzinflate() as needed. See bug #15305\r
625         $header = unpack('n', substr($data, 0, 2));\r
626         return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data);\r
627     }\r
628 }\r
629 ?>