source: extensions/Google2Piwigo/include/Zend/Http/Response.php @ 17475

Last change on this file since 17475 was 17475, checked in by mistic100, 12 years ago

new extension: Google2Piwigo

File size: 18.2 KB
Line 
1<?php
2
3/**
4 * Zend Framework
5 *
6 * LICENSE
7 *
8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
15 *
16 * @category   Zend
17 * @package    Zend_Http
18 * @subpackage Response
19 * @version    $Id: Response.php 24594 2012-01-05 21:27:01Z matthew $
20 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
21 * @license    http://framework.zend.com/license/new-bsd     New BSD License
22 */
23
24/**
25 * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
26 * includes easy access to all the response's different elemts, as well as some
27 * convenience methods for parsing and validating HTTP responses.
28 *
29 * @package    Zend_Http
30 * @subpackage Response
31 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
32 * @license    http://framework.zend.com/license/new-bsd     New BSD License
33 */
34class Zend_Http_Response
35{
36    /**
37     * List of all known HTTP response codes - used by responseCodeAsText() to
38     * translate numeric codes to messages.
39     *
40     * @var array
41     */
42    protected static $messages = array(
43        // Informational 1xx
44        100 => 'Continue',
45        101 => 'Switching Protocols',
46
47        // Success 2xx
48        200 => 'OK',
49        201 => 'Created',
50        202 => 'Accepted',
51        203 => 'Non-Authoritative Information',
52        204 => 'No Content',
53        205 => 'Reset Content',
54        206 => 'Partial Content',
55
56        // Redirection 3xx
57        300 => 'Multiple Choices',
58        301 => 'Moved Permanently',
59        302 => 'Found',  // 1.1
60        303 => 'See Other',
61        304 => 'Not Modified',
62        305 => 'Use Proxy',
63        // 306 is deprecated but reserved
64        307 => 'Temporary Redirect',
65
66        // Client Error 4xx
67        400 => 'Bad Request',
68        401 => 'Unauthorized',
69        402 => 'Payment Required',
70        403 => 'Forbidden',
71        404 => 'Not Found',
72        405 => 'Method Not Allowed',
73        406 => 'Not Acceptable',
74        407 => 'Proxy Authentication Required',
75        408 => 'Request Timeout',
76        409 => 'Conflict',
77        410 => 'Gone',
78        411 => 'Length Required',
79        412 => 'Precondition Failed',
80        413 => 'Request Entity Too Large',
81        414 => 'Request-URI Too Long',
82        415 => 'Unsupported Media Type',
83        416 => 'Requested Range Not Satisfiable',
84        417 => 'Expectation Failed',
85
86        // Server Error 5xx
87        500 => 'Internal Server Error',
88        501 => 'Not Implemented',
89        502 => 'Bad Gateway',
90        503 => 'Service Unavailable',
91        504 => 'Gateway Timeout',
92        505 => 'HTTP Version Not Supported',
93        509 => 'Bandwidth Limit Exceeded'
94    );
95
96    /**
97     * The HTTP version (1.0, 1.1)
98     *
99     * @var string
100     */
101    protected $version;
102
103    /**
104     * The HTTP response code
105     *
106     * @var int
107     */
108    protected $code;
109
110    /**
111     * The HTTP response code as string
112     * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
113     *
114     * @var string
115     */
116    protected $message;
117
118    /**
119     * The HTTP response headers array
120     *
121     * @var array
122     */
123    protected $headers = array();
124
125    /**
126     * The HTTP response body
127     *
128     * @var string
129     */
130    protected $body;
131
132    /**
133     * HTTP response constructor
134     *
135     * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
136     * response string and create a new Zend_Http_Response object.
137     *
138     * NOTE: The constructor no longer accepts nulls or empty values for the code and
139     * headers and will throw an exception if the passed values do not form a valid HTTP
140     * responses.
141     *
142     * If no message is passed, the message will be guessed according to the response code.
143     *
144     * @param int    $code Response code (200, 404, ...)
145     * @param array  $headers Headers array
146     * @param string $body Response body
147     * @param string $version HTTP version
148     * @param string $message Response code as text
149     * @throws Zend_Http_Exception
150     */
151    public function __construct($code, array $headers, $body = null, $version = '1.1', $message = null)
152    {
153        // Make sure the response code is valid and set it
154        if (self::responseCodeAsText($code) === null) {
155            require_once 'Zend/Http/Exception.php';
156            throw new Zend_Http_Exception("{$code} is not a valid HTTP response code");
157        }
158
159        $this->code = $code;
160
161        foreach ($headers as $name => $value) {
162            if (is_int($name)) {
163                $header = explode(":", $value, 2);
164                if (count($header) != 2) {
165                    require_once 'Zend/Http/Exception.php';
166                    throw new Zend_Http_Exception("'{$value}' is not a valid HTTP header");
167                }
168
169                $name  = trim($header[0]);
170                $value = trim($header[1]);
171            }
172
173            $this->headers[ucwords(strtolower($name))] = $value;
174        }
175
176        // Set the body
177        $this->body = $body;
178
179        // Set the HTTP version
180        if (! preg_match('|^\d\.\d$|', $version)) {
181            require_once 'Zend/Http/Exception.php';
182            throw new Zend_Http_Exception("Invalid HTTP response version: $version");
183        }
184
185        $this->version = $version;
186
187        // If we got the response message, set it. Else, set it according to
188        // the response code
189        if (is_string($message)) {
190            $this->message = $message;
191        } else {
192            $this->message = self::responseCodeAsText($code);
193        }
194    }
195
196    /**
197     * Check whether the response is an error
198     *
199     * @return boolean
200     */
201    public function isError()
202    {
203        $restype = floor($this->code / 100);
204        if ($restype == 4 || $restype == 5) {
205            return true;
206        }
207
208        return false;
209    }
210
211    /**
212     * Check whether the response in successful
213     *
214     * @return boolean
215     */
216    public function isSuccessful()
217    {
218        $restype = floor($this->code / 100);
219        if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
220            return true;
221        }
222
223        return false;
224    }
225
226    /**
227     * Check whether the response is a redirection
228     *
229     * @return boolean
230     */
231    public function isRedirect()
232    {
233        $restype = floor($this->code / 100);
234        if ($restype == 3) {
235            return true;
236        }
237
238        return false;
239    }
240
241    /**
242     * Get the response body as string
243     *
244     * This method returns the body of the HTTP response (the content), as it
245     * should be in it's readable version - that is, after decoding it (if it
246     * was decoded), deflating it (if it was gzip compressed), etc.
247     *
248     * If you want to get the raw body (as transfered on wire) use
249     * $this->getRawBody() instead.
250     *
251     * @return string
252     */
253    public function getBody()
254    {
255        $body = '';
256
257        // Decode the body if it was transfer-encoded
258        switch (strtolower($this->getHeader('transfer-encoding'))) {
259
260            // Handle chunked body
261            case 'chunked':
262                $body = self::decodeChunkedBody($this->body);
263                break;
264
265            // No transfer encoding, or unknown encoding extension:
266            // return body as is
267            default:
268                $body = $this->body;
269                break;
270        }
271
272        // Decode any content-encoding (gzip or deflate) if needed
273        switch (strtolower($this->getHeader('content-encoding'))) {
274
275            // Handle gzip encoding
276            case 'gzip':
277                $body = self::decodeGzip($body);
278                break;
279
280            // Handle deflate encoding
281            case 'deflate':
282                $body = self::decodeDeflate($body);
283                break;
284
285            default:
286                break;
287        }
288
289        return $body;
290    }
291
292    /**
293     * Get the raw response body (as transfered "on wire") as string
294     *
295     * If the body is encoded (with Transfer-Encoding, not content-encoding -
296     * IE "chunked" body), gzip compressed, etc. it will not be decoded.
297     *
298     * @return string
299     */
300    public function getRawBody()
301    {
302        return $this->body;
303    }
304
305    /**
306     * Get the HTTP version of the response
307     *
308     * @return string
309     */
310    public function getVersion()
311    {
312        return $this->version;
313    }
314
315    /**
316     * Get the HTTP response status code
317     *
318     * @return int
319     */
320    public function getStatus()
321    {
322        return $this->code;
323    }
324
325    /**
326     * Return a message describing the HTTP response code
327     * (Eg. "OK", "Not Found", "Moved Permanently")
328     *
329     * @return string
330     */
331    public function getMessage()
332    {
333        return $this->message;
334    }
335
336    /**
337     * Get the response headers
338     *
339     * @return array
340     */
341    public function getHeaders()
342    {
343        return $this->headers;
344    }
345
346    /**
347     * Get a specific header as string, or null if it is not set
348     *
349     * @param string$header
350     * @return string|array|null
351     */
352    public function getHeader($header)
353    {
354        $header = ucwords(strtolower($header));
355        if (! is_string($header) || ! isset($this->headers[$header])) return null;
356
357        return $this->headers[$header];
358    }
359
360    /**
361     * Get all headers as string
362     *
363     * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
364     * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
365     * @return string
366     */
367    public function getHeadersAsString($status_line = true, $br = "\n")
368    {
369        $str = '';
370
371        if ($status_line) {
372            $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
373        }
374
375        // Iterate over the headers and stringify them
376        foreach ($this->headers as $name => $value)
377        {
378            if (is_string($value))
379                $str .= "{$name}: {$value}{$br}";
380
381            elseif (is_array($value)) {
382                foreach ($value as $subval) {
383                    $str .= "{$name}: {$subval}{$br}";
384                }
385            }
386        }
387
388        return $str;
389    }
390
391    /**
392     * Get the entire response as string
393     *
394     * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
395     * @return string
396     */
397    public function asString($br = "\n")
398    {
399        return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
400    }
401
402    /**
403     * Implements magic __toString()
404     *
405     * @return string
406     */
407    public function __toString()
408    {
409        return $this->asString();
410    }
411
412    /**
413     * A convenience function that returns a text representation of
414     * HTTP response codes. Returns 'Unknown' for unknown codes.
415     * Returns array of all codes, if $code is not specified.
416     *
417     * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
418     * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
419     *
420     * @param int $code HTTP response code
421     * @param boolean $http11 Use HTTP version 1.1
422     * @return string
423     */
424    public static function responseCodeAsText($code = null, $http11 = true)
425    {
426        $messages = self::$messages;
427        if (! $http11) $messages[302] = 'Moved Temporarily';
428
429        if ($code === null) {
430            return $messages;
431        } elseif (isset($messages[$code])) {
432            return $messages[$code];
433        } else {
434            return 'Unknown';
435        }
436    }
437
438    /**
439     * Extract the response code from a response string
440     *
441     * @param string $response_str
442     * @return int
443     */
444    public static function extractCode($response_str)
445    {
446        preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
447
448        if (isset($m[1])) {
449            return (int) $m[1];
450        } else {
451            return false;
452        }
453    }
454
455    /**
456     * Extract the HTTP message from a response
457     *
458     * @param string $response_str
459     * @return string
460     */
461    public static function extractMessage($response_str)
462    {
463        preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
464
465        if (isset($m[1])) {
466            return $m[1];
467        } else {
468            return false;
469        }
470    }
471
472    /**
473     * Extract the HTTP version from a response
474     *
475     * @param string $response_str
476     * @return string
477     */
478    public static function extractVersion($response_str)
479    {
480        preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
481
482        if (isset($m[1])) {
483            return $m[1];
484        } else {
485            return false;
486        }
487    }
488
489    /**
490     * Extract the headers from a response string
491     *
492     * @param   string $response_str
493     * @return  array
494     */
495    public static function extractHeaders($response_str)
496    {
497        $headers = array();
498
499        // First, split body and headers
500        $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
501        if (! $parts[0]) return $headers;
502
503        // Split headers part to lines
504        $lines = explode("\n", $parts[0]);
505        unset($parts);
506        $last_header = null;
507
508        foreach($lines as $line) {
509            $line = trim($line, "\r\n");
510            if ($line == "") break;
511
512            // Locate headers like 'Location: ...' and 'Location:...' (note the missing space)
513            if (preg_match("|^([\w-]+):\s*(.+)|", $line, $m)) {
514                unset($last_header);
515                $h_name = strtolower($m[1]);
516                $h_value = $m[2];
517
518                if (isset($headers[$h_name])) {
519                    if (! is_array($headers[$h_name])) {
520                        $headers[$h_name] = array($headers[$h_name]);
521                    }
522
523                    $headers[$h_name][] = $h_value;
524                } else {
525                    $headers[$h_name] = $h_value;
526                }
527                $last_header = $h_name;
528            } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
529                if (is_array($headers[$last_header])) {
530                    end($headers[$last_header]);
531                    $last_header_key = key($headers[$last_header]);
532                    $headers[$last_header][$last_header_key] .= $m[1];
533                } else {
534                    $headers[$last_header] .= $m[1];
535                }
536            }
537        }
538
539        return $headers;
540    }
541
542    /**
543     * Extract the body from a response string
544     *
545     * @param string $response_str
546     * @return string
547     */
548    public static function extractBody($response_str)
549    {
550        $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
551        if (isset($parts[1])) {
552            return $parts[1];
553        }
554        return '';
555    }
556
557    /**
558     * Decode a "chunked" transfer-encoded body and return the decoded text
559     *
560     * @param string $body
561     * @return string
562     */
563    public static function decodeChunkedBody($body)
564    {
565        $decBody = '';
566
567        // If mbstring overloads substr and strlen functions, we have to
568        // override it's internal encoding
569        if (function_exists('mb_internal_encoding') &&
570           ((int) ini_get('mbstring.func_overload')) & 2) {
571
572            $mbIntEnc = mb_internal_encoding();
573            mb_internal_encoding('ASCII');
574        }
575
576        while (trim($body)) {
577            if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
578                require_once 'Zend/Http/Exception.php';
579                throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message");
580            }
581
582            $length = hexdec(trim($m[1]));
583            $cut = strlen($m[0]);
584            $decBody .= substr($body, $cut, $length);
585            $body = substr($body, $cut + $length + 2);
586        }
587
588        if (isset($mbIntEnc)) {
589            mb_internal_encoding($mbIntEnc);
590        }
591
592        return $decBody;
593    }
594
595    /**
596     * Decode a gzip encoded message (when Content-encoding = gzip)
597     *
598     * Currently requires PHP with zlib support
599     *
600     * @param string $body
601     * @return string
602     */
603    public static function decodeGzip($body)
604    {
605        if (! function_exists('gzinflate')) {
606            require_once 'Zend/Http/Exception.php';
607            throw new Zend_Http_Exception(
608                'zlib extension is required in order to decode "gzip" encoding'
609            );
610        }
611
612        return gzinflate(substr($body, 10));
613    }
614
615    /**
616     * Decode a zlib deflated message (when Content-encoding = deflate)
617     *
618     * Currently requires PHP with zlib support
619     *
620     * @param string $body
621     * @return string
622     */
623    public static function decodeDeflate($body)
624    {
625        if (! function_exists('gzuncompress')) {
626            require_once 'Zend/Http/Exception.php';
627            throw new Zend_Http_Exception(
628                'zlib extension is required in order to decode "deflate" encoding'
629            );
630        }
631
632        /**
633         * Some servers (IIS ?) send a broken deflate response, without the
634         * RFC-required zlib header.
635         *
636         * We try to detect the zlib header, and if it does not exsit we
637         * teat the body is plain DEFLATE content.
638         *
639         * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
640         *
641         * @link http://framework.zend.com/issues/browse/ZF-6040
642         */
643        $zlibHeader = unpack('n', substr($body, 0, 2));
644        if ($zlibHeader[1] % 31 == 0) {
645            return gzuncompress($body);
646        } else {
647            return gzinflate($body);
648        }
649    }
650
651    /**
652     * Create a new Zend_Http_Response object from a string
653     *
654     * @param string $response_str
655     * @return Zend_Http_Response
656     */
657    public static function fromString($response_str)
658    {
659        $code    = self::extractCode($response_str);
660        $headers = self::extractHeaders($response_str);
661        $body    = self::extractBody($response_str);
662        $version = self::extractVersion($response_str);
663        $message = self::extractMessage($response_str);
664
665        return new Zend_Http_Response($code, $headers, $body, $version, $message);
666    }
667}
Note: See TracBrowser for help on using the repository browser.