web/lib/Zend/Http/Response.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/Zend/Http/Response.php	Fri Mar 11 15:05:35 2011 +0100
@@ -0,0 +1,667 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Http
+ * @subpackage Response
+ * @version    $Id: Response.php 22810 2010-08-08 10:29:09Z shahar $
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
+ * includes easy access to all the response's different elemts, as well as some
+ * convenience methods for parsing and validating HTTP responses.
+ *
+ * @package    Zend_Http
+ * @subpackage Response
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Http_Response
+{
+    /**
+     * List of all known HTTP response codes - used by responseCodeAsText() to
+     * translate numeric codes to messages.
+     *
+     * @var array
+     */
+    protected static $messages = array(
+        // Informational 1xx
+        100 => 'Continue',
+        101 => 'Switching Protocols',
+
+        // Success 2xx
+        200 => 'OK',
+        201 => 'Created',
+        202 => 'Accepted',
+        203 => 'Non-Authoritative Information',
+        204 => 'No Content',
+        205 => 'Reset Content',
+        206 => 'Partial Content',
+
+        // Redirection 3xx
+        300 => 'Multiple Choices',
+        301 => 'Moved Permanently',
+        302 => 'Found',  // 1.1
+        303 => 'See Other',
+        304 => 'Not Modified',
+        305 => 'Use Proxy',
+        // 306 is deprecated but reserved
+        307 => 'Temporary Redirect',
+
+        // Client Error 4xx
+        400 => 'Bad Request',
+        401 => 'Unauthorized',
+        402 => 'Payment Required',
+        403 => 'Forbidden',
+        404 => 'Not Found',
+        405 => 'Method Not Allowed',
+        406 => 'Not Acceptable',
+        407 => 'Proxy Authentication Required',
+        408 => 'Request Timeout',
+        409 => 'Conflict',
+        410 => 'Gone',
+        411 => 'Length Required',
+        412 => 'Precondition Failed',
+        413 => 'Request Entity Too Large',
+        414 => 'Request-URI Too Long',
+        415 => 'Unsupported Media Type',
+        416 => 'Requested Range Not Satisfiable',
+        417 => 'Expectation Failed',
+
+        // Server Error 5xx
+        500 => 'Internal Server Error',
+        501 => 'Not Implemented',
+        502 => 'Bad Gateway',
+        503 => 'Service Unavailable',
+        504 => 'Gateway Timeout',
+        505 => 'HTTP Version Not Supported',
+        509 => 'Bandwidth Limit Exceeded'
+    );
+
+    /**
+     * The HTTP version (1.0, 1.1)
+     *
+     * @var string
+     */
+    protected $version;
+
+    /**
+     * The HTTP response code
+     *
+     * @var int
+     */
+    protected $code;
+
+    /**
+     * The HTTP response code as string
+     * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
+     *
+     * @var string
+     */
+    protected $message;
+
+    /**
+     * The HTTP response headers array
+     *
+     * @var array
+     */
+    protected $headers = array();
+
+    /**
+     * The HTTP response body
+     *
+     * @var string
+     */
+    protected $body;
+
+    /**
+     * HTTP response constructor
+     *
+     * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
+     * response string and create a new Zend_Http_Response object.
+     *
+     * NOTE: The constructor no longer accepts nulls or empty values for the code and
+     * headers and will throw an exception if the passed values do not form a valid HTTP
+     * responses.
+     *
+     * If no message is passed, the message will be guessed according to the response code.
+     *
+     * @param int    $code Response code (200, 404, ...)
+     * @param array  $headers Headers array
+     * @param string $body Response body
+     * @param string $version HTTP version
+     * @param string $message Response code as text
+     * @throws Zend_Http_Exception
+     */
+    public function __construct($code, array $headers, $body = null, $version = '1.1', $message = null)
+    {
+        // Make sure the response code is valid and set it
+        if (self::responseCodeAsText($code) === null) {
+            require_once 'Zend/Http/Exception.php';
+            throw new Zend_Http_Exception("{$code} is not a valid HTTP response code");
+        }
+
+        $this->code = $code;
+
+        foreach ($headers as $name => $value) {
+            if (is_int($name)) {
+                $header = explode(":", $value, 2);
+                if (count($header) != 2) {
+                    require_once 'Zend/Http/Exception.php';
+                    throw new Zend_Http_Exception("'{$value}' is not a valid HTTP header");
+                }
+                
+                $name  = trim($header[0]);
+                $value = trim($header[1]);
+            }
+
+            $this->headers[ucwords(strtolower($name))] = $value;
+        }
+
+        // Set the body
+        $this->body = $body;
+
+        // Set the HTTP version
+        if (! preg_match('|^\d\.\d$|', $version)) {
+            require_once 'Zend/Http/Exception.php';
+            throw new Zend_Http_Exception("Invalid HTTP response version: $version");
+        }
+
+        $this->version = $version;
+
+        // If we got the response message, set it. Else, set it according to
+        // the response code
+        if (is_string($message)) {
+            $this->message = $message;
+        } else {
+            $this->message = self::responseCodeAsText($code);
+        }
+    }
+
+    /**
+     * Check whether the response is an error
+     *
+     * @return boolean
+     */
+    public function isError()
+    {
+        $restype = floor($this->code / 100);
+        if ($restype == 4 || $restype == 5) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check whether the response in successful
+     *
+     * @return boolean
+     */
+    public function isSuccessful()
+    {
+        $restype = floor($this->code / 100);
+        if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check whether the response is a redirection
+     *
+     * @return boolean
+     */
+    public function isRedirect()
+    {
+        $restype = floor($this->code / 100);
+        if ($restype == 3) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the response body as string
+     *
+     * This method returns the body of the HTTP response (the content), as it
+     * should be in it's readable version - that is, after decoding it (if it
+     * was decoded), deflating it (if it was gzip compressed), etc.
+     *
+     * If you want to get the raw body (as transfered on wire) use
+     * $this->getRawBody() instead.
+     *
+     * @return string
+     */
+    public function getBody()
+    {
+        $body = '';
+
+        // Decode the body if it was transfer-encoded
+        switch (strtolower($this->getHeader('transfer-encoding'))) {
+
+            // Handle chunked body
+            case 'chunked':
+                $body = self::decodeChunkedBody($this->body);
+                break;
+
+            // No transfer encoding, or unknown encoding extension:
+            // return body as is
+            default:
+                $body = $this->body;
+                break;
+        }
+
+        // Decode any content-encoding (gzip or deflate) if needed
+        switch (strtolower($this->getHeader('content-encoding'))) {
+
+            // Handle gzip encoding
+            case 'gzip':
+                $body = self::decodeGzip($body);
+                break;
+
+            // Handle deflate encoding
+            case 'deflate':
+                $body = self::decodeDeflate($body);
+                break;
+
+            default:
+                break;
+        }
+
+        return $body;
+    }
+
+    /**
+     * Get the raw response body (as transfered "on wire") as string
+     *
+     * If the body is encoded (with Transfer-Encoding, not content-encoding -
+     * IE "chunked" body), gzip compressed, etc. it will not be decoded.
+     *
+     * @return string
+     */
+    public function getRawBody()
+    {
+        return $this->body;
+    }
+
+    /**
+     * Get the HTTP version of the response
+     *
+     * @return string
+     */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * Get the HTTP response status code
+     *
+     * @return int
+     */
+    public function getStatus()
+    {
+        return $this->code;
+    }
+
+    /**
+     * Return a message describing the HTTP response code
+     * (Eg. "OK", "Not Found", "Moved Permanently")
+     *
+     * @return string
+     */
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+    /**
+     * Get the response headers
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Get a specific header as string, or null if it is not set
+     *
+     * @param string$header
+     * @return string|array|null
+     */
+    public function getHeader($header)
+    {
+        $header = ucwords(strtolower($header));
+        if (! is_string($header) || ! isset($this->headers[$header])) return null;
+
+        return $this->headers[$header];
+    }
+
+    /**
+     * Get all headers as string
+     *
+     * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
+     * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
+     * @return string
+     */
+    public function getHeadersAsString($status_line = true, $br = "\n")
+    {
+        $str = '';
+
+        if ($status_line) {
+            $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
+        }
+
+        // Iterate over the headers and stringify them
+        foreach ($this->headers as $name => $value)
+        {
+            if (is_string($value))
+                $str .= "{$name}: {$value}{$br}";
+
+            elseif (is_array($value)) {
+                foreach ($value as $subval) {
+                    $str .= "{$name}: {$subval}{$br}";
+                }
+            }
+        }
+
+        return $str;
+    }
+
+    /**
+     * Get the entire response as string
+     *
+     * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
+     * @return string
+     */
+    public function asString($br = "\n")
+    {
+        return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
+    }
+
+    /**
+     * Implements magic __toString()
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->asString();
+    }
+
+    /**
+     * A convenience function that returns a text representation of
+     * HTTP response codes. Returns 'Unknown' for unknown codes.
+     * Returns array of all codes, if $code is not specified.
+     *
+     * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
+     * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
+     *
+     * @param int $code HTTP response code
+     * @param boolean $http11 Use HTTP version 1.1
+     * @return string
+     */
+    public static function responseCodeAsText($code = null, $http11 = true)
+    {
+        $messages = self::$messages;
+        if (! $http11) $messages[302] = 'Moved Temporarily';
+
+        if ($code === null) {
+            return $messages;
+        } elseif (isset($messages[$code])) {
+            return $messages[$code];
+        } else {
+            return 'Unknown';
+        }
+    }
+
+    /**
+     * Extract the response code from a response string
+     *
+     * @param string $response_str
+     * @return int
+     */
+    public static function extractCode($response_str)
+    {
+        preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
+
+        if (isset($m[1])) {
+            return (int) $m[1];
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Extract the HTTP message from a response
+     *
+     * @param string $response_str
+     * @return string
+     */
+    public static function extractMessage($response_str)
+    {
+        preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
+
+        if (isset($m[1])) {
+            return $m[1];
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Extract the HTTP version from a response
+     *
+     * @param string $response_str
+     * @return string
+     */
+    public static function extractVersion($response_str)
+    {
+        preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
+
+        if (isset($m[1])) {
+            return $m[1];
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Extract the headers from a response string
+     *
+     * @param   string $response_str
+     * @return  array
+     */
+    public static function extractHeaders($response_str)
+    {
+        $headers = array();
+
+        // First, split body and headers
+        $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
+        if (! $parts[0]) return $headers;
+
+        // Split headers part to lines
+        $lines = explode("\n", $parts[0]);
+        unset($parts);
+        $last_header = null;
+
+        foreach($lines as $line) {
+            $line = trim($line, "\r\n");
+            if ($line == "") break;
+
+            // Locate headers like 'Location: ...' and 'Location:...' (note the missing space)
+            if (preg_match("|^([\w-]+):\s*(.+)|", $line, $m)) {
+                unset($last_header);
+                $h_name = strtolower($m[1]);
+                $h_value = $m[2];
+
+                if (isset($headers[$h_name])) {
+                    if (! is_array($headers[$h_name])) {
+                        $headers[$h_name] = array($headers[$h_name]);
+                    }
+
+                    $headers[$h_name][] = $h_value;
+                } else {
+                    $headers[$h_name] = $h_value;
+                }
+                $last_header = $h_name;
+            } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
+                if (is_array($headers[$last_header])) {
+                    end($headers[$last_header]);
+                    $last_header_key = key($headers[$last_header]);
+                    $headers[$last_header][$last_header_key] .= $m[1];
+                } else {
+                    $headers[$last_header] .= $m[1];
+                }
+            }
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Extract the body from a response string
+     *
+     * @param string $response_str
+     * @return string
+     */
+    public static function extractBody($response_str)
+    {
+        $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
+        if (isset($parts[1])) {
+            return $parts[1];
+        }
+        return '';
+    }
+
+    /**
+     * Decode a "chunked" transfer-encoded body and return the decoded text
+     *
+     * @param string $body
+     * @return string
+     */
+    public static function decodeChunkedBody($body)
+    {
+        $decBody = '';
+
+        // If mbstring overloads substr and strlen functions, we have to
+        // override it's internal encoding
+        if (function_exists('mb_internal_encoding') &&
+           ((int) ini_get('mbstring.func_overload')) & 2) {
+
+            $mbIntEnc = mb_internal_encoding();
+            mb_internal_encoding('ASCII');
+        }
+
+        while (trim($body)) {
+            if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
+                require_once 'Zend/Http/Exception.php';
+                throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message");
+            }
+
+            $length = hexdec(trim($m[1]));
+            $cut = strlen($m[0]);
+            $decBody .= substr($body, $cut, $length);
+            $body = substr($body, $cut + $length + 2);
+        }
+
+        if (isset($mbIntEnc)) {
+            mb_internal_encoding($mbIntEnc);
+        }
+
+        return $decBody;
+    }
+
+    /**
+     * Decode a gzip encoded message (when Content-encoding = gzip)
+     *
+     * Currently requires PHP with zlib support
+     *
+     * @param string $body
+     * @return string
+     */
+    public static function decodeGzip($body)
+    {
+        if (! function_exists('gzinflate')) {
+            require_once 'Zend/Http/Exception.php';
+            throw new Zend_Http_Exception(
+                'zlib extension is required in order to decode "gzip" encoding'
+            );
+        }
+
+        return gzinflate(substr($body, 10));
+    }
+
+    /**
+     * Decode a zlib deflated message (when Content-encoding = deflate)
+     *
+     * Currently requires PHP with zlib support
+     *
+     * @param string $body
+     * @return string
+     */
+    public static function decodeDeflate($body)
+    {
+        if (! function_exists('gzuncompress')) {
+            require_once 'Zend/Http/Exception.php';
+            throw new Zend_Http_Exception(
+                'zlib extension is required in order to decode "deflate" encoding'
+            );
+        }
+
+        /**
+         * Some servers (IIS ?) send a broken deflate response, without the
+         * RFC-required zlib header.
+         *
+         * We try to detect the zlib header, and if it does not exsit we
+         * teat the body is plain DEFLATE content.
+         *
+         * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
+         *
+         * @link http://framework.zend.com/issues/browse/ZF-6040
+         */
+        $zlibHeader = unpack('n', substr($body, 0, 2));
+        if ($zlibHeader[1] % 31 == 0) {
+            return gzuncompress($body);
+        } else {
+            return gzinflate($body);
+        }
+    }
+
+    /**
+     * Create a new Zend_Http_Response object from a string
+     *
+     * @param string $response_str
+     * @return Zend_Http_Response
+     */
+    public static function fromString($response_str)
+    {
+        $code    = self::extractCode($response_str);
+        $headers = self::extractHeaders($response_str);
+        $body    = self::extractBody($response_str);
+        $version = self::extractVersion($response_str);
+        $message = self::extractMessage($response_str);
+
+        return new Zend_Http_Response($code, $headers, $body, $version, $message);
+    }
+}