text:
<?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 Cookie
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @version    $Id: Cookie.php 23775 2011-03-01 17:25:24Z ralph $
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * @see Zend_Uri_Http
 */
require_once 'Zend/Uri/Http.php';

/**
 * Zend_Http_Cookie is a class describing an HTTP cookie and all it's parameters.
 *
 * Zend_Http_Cookie is a class describing an HTTP cookie and all it's parameters. The
 * class also enables validating whether the cookie should be sent to the server in
 * a specified scenario according to the request URI, the expiry time and whether
 * session cookies should be used or not. Generally speaking cookies should be
 * contained in a Cookiejar object, or instantiated manually and added to an HTTP
 * request.
 *
 * See http://wp.netscape.com/newsref/std/cookie_spec.html for some specs.
 *
 * @category   Zend
 * @package    Zend_Http
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Cookie
{
    /**
     * Cookie name
     *
     * @var string
     */
    protected $name;

    /**
     * Cookie value
     *
     * @var string
     */
    protected $value;

    /**
     * Cookie expiry date
     *
     * @var int
     */
    protected $expires;

    /**
     * Cookie domain
     *
     * @var string
     */
    protected $domain;

    /**
     * Cookie path
     *
     * @var string
     */
    protected $path;

    /**
     * Whether the cookie is secure or not
     *
     * @var boolean
     */
    protected $secure;

    /**
     * Whether the cookie value has been encoded/decoded
     *
     * @var boolean
     */
    protected $encodeValue;

    /**
     * Cookie object constructor
     *
     * @todo Add validation of each one of the parameters (legal domain, etc.)
     *
     * @param string $name
     * @param string $value
     * @param string $domain
     * @param int $expires
     * @param string $path
     * @param bool $secure
     */
    public function __construct($name, $value, $domain, $expires = null, $path = null, $secure = false)
    {
        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception("Cookie name cannot contain these characters: =,; \\t\\r\\n\\013\\014 ({$name})");
        }

        if (! $this->name = (string) $name) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception('Cookies must have a name');
        }

        if (! $this->domain = (string) $domain) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception('Cookies must have a domain');
        }

        $this->value = (string) $value;
        $this->expires = ($expires === null ? null : (int) $expires);
        $this->path = ($path ? $path : '/');
        $this->secure = $secure;
    }

    /**
     * Get Cookie name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get cookie value
     *
     * @return string
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Get cookie domain
     *
     * @return string
     */
    public function getDomain()
    {
        return $this->domain;
    }

    /**
     * Get the cookie path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Get the expiry time of the cookie, or null if no expiry time is set
     *
     * @return int|null
     */
    public function getExpiryTime()
    {
        return $this->expires;
    }

    /**
     * Check whether the cookie should only be sent over secure connections
     *
     * @return boolean
     */
    public function isSecure()
    {
        return $this->secure;
    }

    /**
     * Check whether the cookie has expired
     *
     * Always returns false if the cookie is a session cookie (has no expiry time)
     *
     * @param int $now Timestamp to consider as "now"
     * @return boolean
     */
    public function isExpired($now = null)
    {
        if ($now === null) $now = time();
        if (is_int($this->expires) && $this->expires < $now) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Check whether the cookie is a session cookie (has no expiry time set)
     *
     * @return boolean
     */
    public function isSessionCookie()
    {
        return ($this->expires === null);
    }

    /**
     * Checks whether the cookie should be sent or not in a specific scenario
     *
     * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path)
     * @param boolean $matchSessionCookies Whether to send session cookies
     * @param int $now Override the current time when checking for expiry time
     * @return boolean
     */
    public function match($uri, $matchSessionCookies = true, $now = null)
    {
        if (is_string ($uri)) {
            $uri = Zend_Uri_Http::factory($uri);
        }

        // Make sure we have a valid Zend_Uri_Http object
        if (! ($uri->valid() && ($uri->getScheme() == 'http' || $uri->getScheme() =='https'))) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception('Passed URI is not a valid HTTP or HTTPS URI');
        }

        // Check that the cookie is secure (if required) and not expired
        if ($this->secure && $uri->getScheme() != 'https') return false;
        if ($this->isExpired($now)) return false;
        if ($this->isSessionCookie() && ! $matchSessionCookies) return false;

        // Check if the domain matches
        if (! self::matchCookieDomain($this->getDomain(), $uri->getHost())) {
            return false;
        }

        // Check that path matches using prefix match
        if (! self::matchCookiePath($this->getPath(), $uri->getPath())) {
            return false;
        }

        // If we didn't die until now, return true.
        return true;
    }

    /**
     * Get the cookie as a string, suitable for sending as a "Cookie" header in an
     * HTTP request
     *
     * @return string
     */
    public function __toString()
    {
        if ($this->encodeValue) {
            return $this->name . '=' . urlencode($this->value) . ';';
        }
        return $this->name . '=' . $this->value . ';';
    }

    /**
     * Generate a new Cookie object from a cookie string
     * (for example the value of the Set-Cookie HTTP header)
     *
     * @param string $cookieStr
     * @param Zend_Uri_Http|string $refUri Reference URI for default values (domain, path)
     * @param boolean $encodeValue Whether or not the cookie's value should be
     *                             passed through urlencode/urldecode
     * @return Zend_Http_Cookie A new Zend_Http_Cookie object or false on failure.
     */
    public static function fromString($cookieStr, $refUri = null, $encodeValue = true)
    {
        // Set default values
        if (is_string($refUri)) {
            $refUri = Zend_Uri_Http::factory($refUri);
        }

        $name    = '';
        $value   = '';
        $domain  = '';
        $path    = '';
        $expires = null;
        $secure  = false;
        $parts   = explode(';', $cookieStr);

        // If first part does not include '=', fail
        if (strpos($parts[0], '=') === false) return false;

        // Get the name and value of the cookie
        list($name, $value) = explode('=', trim(array_shift($parts)), 2);
        $name  = trim($name);
        if ($encodeValue) {
            $value = urldecode(trim($value));
        }

        // Set default domain and path
        if ($refUri instanceof Zend_Uri_Http) {
            $domain = $refUri->getHost();
            $path = $refUri->getPath();
            $path = substr($path, 0, strrpos($path, '/'));
        }

        // Set other cookie parameters
        foreach ($parts as $part) {
            $part = trim($part);
            if (strtolower($part) == 'secure') {
                $secure = true;
                continue;
            }

            $keyValue = explode('=', $part, 2);
            if (count($keyValue) == 2) {
                list($k, $v) = $keyValue;
                switch (strtolower($k))    {
                    case 'expires':
                        if(($expires = strtotime($v)) === false) {
                            /**
                             * The expiration is past Tue, 19 Jan 2038 03:14:07 UTC
                             * the maximum for 32-bit signed integer. Zend_Date
                             * can get around that limit.
                             *
                             * @see Zend_Date
                             */
                            require_once 'Zend/Date.php';

                            $expireDate = new Zend_Date($v);
                            $expires = $expireDate->getTimestamp();
                        }
                        break;

                    case 'path':
                        $path = $v;
                        break;

                    case 'domain':
                        $domain = $v;
                        break;

                    default:
                        break;
                }
            }
        }

        if ($name !== '') {
            $ret = new self($name, $value, $domain, $expires, $path, $secure);
            $ret->encodeValue = ($encodeValue) ? true : false;
            return $ret;
        } else {
            return false;
        }
    }

    /**
     * Check if a cookie's domain matches a host name.
     *
     * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching
     *
     * @param  string $cookieDomain
     * @param  string $host
     *
     * @return boolean
     */
    public static function matchCookieDomain($cookieDomain, $host)
    {
        if (! $cookieDomain) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception("\$cookieDomain is expected to be a cookie domain");
        }

        if (! $host) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception("\$host is expected to be a host name");
        }

        $cookieDomain = strtolower($cookieDomain);
        $host = strtolower($host);

        if ($cookieDomain[0] == '.') {
            $cookieDomain = substr($cookieDomain, 1);
        }

        // Check for either exact match or suffix match
        return ($cookieDomain == $host ||
                preg_match('/\.' . preg_quote($cookieDomain) . '$/', $host));
    }

    /**
     * Check if a cookie's path matches a URL path
     *
     * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching
     *
     * @param  string $cookiePath
     * @param  string $path
     * @return boolean
     */
    public static function matchCookiePath($cookiePath, $path)
    {
        if (! $cookiePath) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception("\$cookiePath is expected to be a cookie path");
        }

        if (! $path) {
            require_once 'Zend/Http/Exception.php';
            throw new Zend_Http_Exception("\$path is expected to be a host name");
        }

        return (strpos($path, $cookiePath) === 0);
    }
}


text:
<?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 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 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-2011 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", "
")
     * @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", "
")
     * @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);
    }
}

repo_name: michael-romer/zf-boilerplate

repo_language: PHP

mime_type: text/html


text:
<?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_UserAgent
 * @subpackage UserAgent
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * Lists of User Agent chains for testing :
 *
 * - http://www.useragentstring.com/layout/useragentstring.php
 * - http://user-agent-string.info/list-of-ua
 * - http://www.user-agents.org/allagents.xml
 * - http://en.wikipedia.org/wiki/List_of_user_agents_for_mobile_phones
 * - http://www.mobilemultimedia.be/fr/
 *
 * @category   Zend
 * @package    Zend_Http_UserAgent
 * @subpackage UserAgent
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_UserAgent implements Serializable
{
    /**
     * 'desktop' by default if the sequence return false for each item or is empty
     */
    const DEFAULT_IDENTIFICATION_SEQUENCE = 'mobile,desktop';

    /**
     * Default persitent storage adapter : Session or NonPersitent
     */
    const DEFAULT_PERSISTENT_STORAGE_ADAPTER = 'Session';

    /**
     * 'desktop' by default if the sequence return false for each item
     */
    const DEFAULT_BROWSER_TYPE = 'desktop';

    /**
     * Default User Agent chain to prevent empty value
     */
    const DEFAULT_HTTP_USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)';

    /**
     * Default Http Accept param to prevent empty value
     */
    const DEFAULT_HTTP_ACCEPT = "application/xhtml+xml";

    /**
     * Default markup language
     */
    const DEFAULT_MARKUP_LANGUAGE = "xhtml";

    /**
     * Browser type
     *
     * @var string
     */
    protected $_browserType;

    /**
     * Browser type class
     *
     * Map of browser types to classes.
     *
     * @var array
     */
    protected $_browserTypeClass = array();

    /**
     * Array to store config
     *
     * Default values are provided to ensure specific keys are present at
     * instantiation.
     *
     * @var array
     */
    protected $_config = array(
        'identification_sequence' => self::DEFAULT_IDENTIFICATION_SEQUENCE,
        'storage'                 => array(
            'adapter'             => self::DEFAULT_PERSISTENT_STORAGE_ADAPTER,
        ),
    );

    /**
     * Identified device
     *
     * @var Zend_Http_UserAgent_Device
     */
    protected $_device;

    /**
     * Whether or not this instance is immutable.
     *
     * If true, none of the following may be modified:
     * - $_server
     * - $_browserType
     * - User-Agent (defined in $_server)
     * - HTTP Accept value (defined in $_server)
     * - $_storage
     *
     * @var bool
     */
    protected $_immutable = false;

    /**
     * Plugin loaders
     * @var array
     */
    protected $_loaders = array();

    /**
     * Valid plugin loader types
     * @var array
     */
    protected $_loaderTypes = array('storage', 'device');

    /**
     * Trace of items matched to identify the browser type
     *
     * @var array
     */
    protected $_matchLog = array();

    /**
     * Server variable
     *
     * @var array
     */
    protected $_server;

    /**
     * Persistent storage handler
     *
     * @var Zend_Http_UserAgent_Storage
     */
    protected $_storage;

    /**
     * Constructor
     *
     * @param  null|array|Zend_Config|ArrayAccess $options
     * @return void
     */
    public function __construct($options = null)
    {
        if (null !== $options) {
            $this->setOptions($options);
        }
    }

    /**
     * Serialized representation of the object
     *
     * @return string
     */
    public function serialize()
    {
        $device = $this->getDevice();
        $spec = array(
            'browser_type' => $this->_browserType,
            'config'       => $this->_config,
            'device_class' => get_class($device),
            'device'       => $device->serialize(),
            'user_agent'   => $this->getServerValue('http_user_agent'),
            'http_accept'  => $this->getServerValue('http_accept'),
        );
        return serialize($spec);
    }

    /**
     * Unserialize a previous representation of the object
     *
     * @param  string $serialized
     * @return void
     */
    public function unserialize($serialized)
    {
        $spec = unserialize($serialized);

        $this->setOptions($spec);

        // Determine device class and ensure the class is loaded
        $deviceClass          = $spec['device_class'];
        if (!class_exists($deviceClass)) {
            $this->_getUserAgentDevice($this->getBrowserType());
        }

        // Get device specification and instantiate
        $deviceSpec            = unserialize($spec['device']);
        $deviceSpec['_config'] = $this->getConfig();
        $deviceSpec['_server'] = $this->getServer();
        $this->_device = new $deviceClass($deviceSpec);
    }

    /**
     * Configure instance
     *
     * @param  array|Zend_Config|ArrayAccess $options
     * @return Zend_Http_UserAgent
     */
    public function setOptions($options)
    {
        if ($options instanceof Zend_Config) {
            $options = $options->toArray();
        }

        if (!is_array($options)
            && !$options instanceof ArrayAccess
            && !$options instanceof Traversable
        ) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Invalid argument; expected array, Zend_Config object, or object implementing ArrayAccess and Traversable; received %s',
                (is_object($options) ? get_class($options) : gettype($options))
            ));
        }

        // Set $_SERVER first
        if (isset($options['server'])) {
            $this->setServer($options['server']);
            unset($options['server']);
        }

        // Get plugin loaders sorted
        if (isset($options['plugin_loader'])) {
            $plConfig = $options['plugin_loader'];
            if (is_array($plConfig) || $plConfig instanceof Traversable) {
                foreach ($plConfig as $type => $class) {
                    $this->setPluginLoader($type, $class);
                }
            }
            unset($plConfig, $options['plugin_loader']);
        }

        // And then loop through the remaining options
        $config = array();
        foreach ($options as $key => $value) {
            switch (strtolower($key)) {
                case 'browser_type':
                    $this->setBrowserType($value);
                    break;
                case 'http_accept':
                    $this->setHttpAccept($value);
                    break;
                case 'user_agent':
                    $this->setUserAgent($value);
                    break;
                default:
                    // Cache remaining options for $_config
                    $config[$key] = $value;
                    break;
            }
        }
        $this->setConfig($config);

        return $this;
    }

    /**
     * Comparison of the UserAgent chain and browser signatures.
     *
     * The comparison is case-insensitive : the browser signatures must be in lower
     * case
     *
     * @param  string $deviceClass Name of class against which a match will be attempted
     * @return bool
     */
    protected function _match($deviceClass)
    {
        // Validate device class
        $r = new ReflectionClass($deviceClass);
        if (!$r->implementsInterface('Zend_Http_UserAgent_Device')) {
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Invalid device class provided ("%s"); must implement Zend_Http_UserAgent_Device',
                $deviceClass
            ));
        }

        $userAgent = $this->getUserAgent();

        // Call match method on device class
        return call_user_func(
            array($deviceClass, 'match'),
            $userAgent,
            $this->getServer()
        );
    }

    /**
     * Loads class for a user agent device
     *
     * @param  string $browserType Browser type
     * @return string
     * @throws Zend_Loader_PluginLoader_Exception if unable to load UA device
     */
    protected function _getUserAgentDevice($browserType)
    {
        $browserType = strtolower($browserType);
        if (isset($this->_browserTypeClass[$browserType])) {
            return $this->_browserTypeClass[$browserType];
        }

        if (isset($this->_config[$browserType])
            && isset($this->_config[$browserType]['device'])
        ) {
            $deviceConfig = $this->_config[$browserType]['device'];
            if (is_array($deviceConfig) && isset($deviceConfig['classname'])) {
                $device = (string) $deviceConfig['classname'];
                if (!class_exists($device)) {
                    require_once 'Zend/Http/UserAgent/Exception.php';
                    throw new Zend_Http_UserAgent_Exception(sprintf(
                        'Invalid classname "%s" provided in device configuration for browser type "%s"',
                        $device,
                        $browserType
                    ));
                }
            } elseif (is_array($deviceConfig) && isset($deviceConfig['path'])) {
                $loader = $this->getPluginLoader('device');
                $path   = $deviceConfig['path'];
                $prefix = isset($deviceConfig['prefix']) ? $deviceConfig['prefix'] : 'Zend_Http_UserAgent';
                $loader->addPrefixPath($prefix, $path);

                $device = $loader->load($browserType);
            } else {
                $loader = $this->getPluginLoader('device');
                $device = $loader->load($browserType);
            }
        } else {
            $loader = $this->getPluginLoader('device');
            $device = $loader->load($browserType);
        }

        $this->_browserTypeClass[$browserType] = $device;

        return $device;
    }

    /**
     * Returns the User Agent value
     *
     * If $userAgent param is null, the value of $_server['HTTP_USER_AGENT'] is
     * returned.
     *
     * @return string
     */
    public function getUserAgent()
    {
        if (null === ($ua = $this->getServerValue('http_user_agent'))) {
            $ua = self::DEFAULT_HTTP_USER_AGENT;
            $this->setUserAgent($ua);
        }

        return $ua;
    }

    /**
     * Force or replace the UA chain in $_server variable
     *
     * @param  string $userAgent Forced UserAgent chain
     * @return Zend_Http_UserAgent
     */
    public function setUserAgent($userAgent)
    {
        $this->setServerValue('http_user_agent', $userAgent);
        return $this;
    }

    /**
     * Returns the HTTP Accept server param
     *
     * @param  string $httpAccept (option) forced HTTP Accept chain
     * @return string
     */
    public function getHttpAccept($httpAccept = null)
    {
        if (null === ($accept = $this->getServerValue('http_accept'))) {
            $accept = self::DEFAULT_HTTP_ACCEPT;
            $this->setHttpAccept($accept);
        }
        return $accept;
    }

    /**
     * Force or replace the HTTP_ACCEPT chain in self::$_server variable
     *
     * @param  string $httpAccept Forced HTTP Accept chain
     * @return Zend_Http_UserAgent
     */
    public function setHttpAccept($httpAccept)
    {
        $this->setServerValue('http_accept', $httpAccept);
        return $this;
    }

    /**
     * Returns the persistent storage handler
     *
     * Session storage is used by default unless a different storage adapter
     * has been set via the "persistent_storage_adapter" key. That key should
     * contain either a fully qualified class name, or a short name that
     * resolves via the plugin loader.
     *
     * @param  string $browser Browser identifier (User Agent chain)
     * @return Zend_Http_UserAgent_Storage
     */
    public function getStorage($browser = null)
    {
        if (null === $browser) {
            $browser = $this->getUserAgent();
        }
        if (null === $this->_storage) {
            $config  = $this->_config['storage'];
            $adapter = $config['adapter'];
            if (!class_exists($adapter)) {
                $loader = $this->getPluginLoader('storage');
                $adapter = $loader->load($adapter);
                $loader = $this->getPluginLoader('storage');
            }
            $options = array('browser_type' => $browser);
            if (isset($config['options'])) {
                $options = array_merge($options, $config['options']);
            }
            $this->setStorage(new $adapter($options));
        }
        return $this->_storage;
    }

    /**
     * Sets the persistent storage handler
     *
     * @param  Zend_Http_UserAgent_Storage $storage
     * @return Zend_Http_UserAgent
     */
    public function setStorage(Zend_Http_UserAgent_Storage $storage)
    {
        if ($this->_immutable) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(
                'The User-Agent device object has already been retrieved; the storage object is now immutable'
            );
        }

        $this->_storage = $storage;
        return $this;
    }

    /**
     * Clean the persistent storage
     *
     * @param  string $browser Browser identifier (User Agent chain)
     * @return void
     */
    public function clearStorage($browser = null)
    {
        $this->getStorage($browser)->clear();
    }

    /**
     * Get user configuration
     *
     * @return array
     */
    public function getConfig()
    {
        return $this->_config;
    }

    /**
     * Config parameters is an Array or a Zend_Config object
     *
     * The allowed parameters are :
     * - the identification sequence (can be empty) => desktop browser type is the
     * default browser type returned
     * $config['identification_sequence'] : ',' separated browser types
     * - the persistent storage adapter
     * $config['persistent_storage_adapter'] = "Session" or "NonPersistent"
     * - to add or replace a browser type device
     * $config[(type)]['device']['path']
     * $config[(type)]['device']['classname']
     * - to add or replace a browser type features adapter
     * $config[(type)]['features']['path']
     * $config[(type)]['features']['classname']
     *
     * @param  mixed $config (option) Config array
     * @return Zend_Http_UserAgent
     */
    public function setConfig($config = array())
    {
        if ($config instanceof Zend_Config) {
            $config = $config->toArray();
        }

        // Verify that Config parameters are in an array.
        if (!is_array($config) && !$config instanceof Traversable) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Config parameters must be in an array or a Traversable object; received "%s"',
                (is_object($config) ? get_class($config) : gettype($config))
            ));
        }

        if ($config instanceof Traversable) {
            $tmp = array();
            foreach ($config as $key => $value) {
                $tmp[$key] = $value;
            }
            $config = $tmp;
            unset($tmp);
        }

        $this->_config = array_merge($this->_config, $config);
        return $this;
    }

    /**
     * Returns the device object
     *
     * This is the object that will contain the various discovered device
     * capabilities.
     *
     * @return Zend_Http_UserAgent_Device $device
     */
    public function getDevice()
    {
        if (null !== $this->_device) {
            return $this->_device;
        }

        $userAgent = $this->getUserAgent();

        // search an existing identification in the session
        $storage = $this->getStorage($userAgent);

        if (!$storage->isEmpty()) {
            // If the user agent and features are already existing, the
            // Zend_Http_UserAgent object is serialized in the session
            $object = $storage->read();
            $this->unserialize($object);
        } else {
            // Otherwise, the identification is made and stored in the session.
            // Find the browser type:
            $this->setBrowserType($this->_matchUserAgent());
            $this->_createDevice();

            // put the result in storage:
            $this->getStorage($userAgent)
                 ->write($this->serialize());
        }

        // Mark the object as immutable
        $this->_immutable = true;

        // Return the device instance
        return $this->_device;
    }

    /**
     * Retrieve the browser type
     *
     * @return string $browserType
     */
    public function getBrowserType()
    {
        return $this->_browserType;
    }

    /**
     * Set the browser "type"
     *
     * @param string $browserType
     * @return Zend_Http_UserAgent
     */
    public function setBrowserType($browserType)
    {
        if ($this->_immutable) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(
                'The User-Agent device object has already been retrieved; the browser type is now immutable'
            );
        }

        $this->_browserType = $browserType;
        return $this;
    }

    /**
     * Retrieve the "$_SERVER" array
     *
     * Basically, the $_SERVER array or an equivalent container storing the
     * data that will be introspected.
     *
     * If the value has not been previously set, it sets itself from the
     * $_SERVER superglobal.
     *
     * @return array
     */
    public function getServer()
    {
        if (null === $this->_server) {
            $this->setServer($_SERVER);
        }
        return $this->_server;
    }

    /**
     * Set the "$_SERVER" array
     *
     * Basically, the $_SERVER array or an equivalent container storing the
     * data that will be introspected.
     *
     * @param  array|ArrayAccess $server
     * @return void
     * @throws Zend_Http_UserAgent_Exception on invalid parameter
     */
    public function setServer($server)
    {
        if ($this->_immutable) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(
                'The User-Agent device object has already been retrieved; the server array is now immutable'
            );
        }

        if (!is_array($server) && !$server instanceof Traversable) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Expected an array or object implementing Traversable; received %s',
                (is_object($server) ? get_class($server) : gettype($server))
            ));
        }

        // Get an array if we don't have one
        if ($server instanceof ArrayObject) {
            $server = $server->getArrayCopy();
        } elseif ($server instanceof Traversable) {
            $tmp = array();
            foreach ($server as $key => $value) {
                $tmp[$key] = $value;
            }
            $server = $tmp;
            unset($tmp);
        }

        // Normalize key case
        $server = array_change_key_case($server, CASE_LOWER);

        $this->_server = $server;
        return $this;
    }

    /**
     * Retrieve a server value
     *
     * @param  string $key
     * @return mixed
     */
    public function getServerValue($key)
    {
        $key    = strtolower($key);
        $server = $this->getServer();
        $return = null;
        if (isset($server[$key])) {
            $return = $server[$key];
        }
        unset($server);
        return $return;
    }

    /**
     * Set a server value
     *
     * @param  string|int|float $key
     * @param  mixed $value
     * @return void
     */
    public function setServerValue($key, $value)
    {
        if ($this->_immutable) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(
                'The User-Agent device object has already been retrieved; the server array is now immutable'
            );
        }

        $server = $this->getServer(); // ensure it's been initialized
        $key    = strtolower($key);
        $this->_server[$key] = $value;
        return $this;
    }

    /**
     * Set plugin loader
     *
     * @param  string $type Type of plugin loader; one of 'storage', (?)
     * @param  string|Zend_Loader_PluginLoader $loader
     * @return Zend_Http_UserAgent
     */
    public function setPluginLoader($type, $loader)
    {
        $type       = $this->_validateLoaderType($type);

        if (is_string($loader)) {
            if (!class_exists($loader)) {
                require_once 'Zend/Loader.php';
                Zend_Loader::loadClass($loader);
            }
            $loader = new $loader();
        } elseif (!is_object($loader)) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Expected a plugin loader class or object; received %s',
                gettype($loader)
            ));
        }
        if (!$loader instanceof Zend_Loader_PluginLoader) {
            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Expected an object extending Zend_Loader_PluginLoader; received %s',
                get_class($loader)
            ));
        }

        $basePrefix = 'Zend_Http_UserAgent_';
        $basePath   = 'Zend/Http/UserAgent/';
        switch ($type) {
            case 'storage':
                $prefix = $basePrefix . 'Storage';
                $path   = $basePath   . 'Storage';
                break;
            case 'device':
                $prefix = $basePrefix;
                $path   = $basePath;
                break;
        }
        $loader->addPrefixPath($prefix, $path);
        $this->_loaders[$type] = $loader;
        return $this;
    }

    /**
     * Get a plugin loader
     *
     * @param  string $type A valid plugin loader type; see {@link $_loaderTypes}
     * @return Zend_Loader_PluginLoader
     */
    public function getPluginLoader($type)
    {
        $type = $this->_validateLoaderType($type);
        if (!isset($this->_loaders[$type])) {
            require_once 'Zend/Loader/PluginLoader.php';
            $this->setPluginLoader($type, new Zend_Loader_PluginLoader());
        }
        return $this->_loaders[$type];
    }

    /**
     * Validate a plugin loader type
     *
     * Verifies that it is in {@link $_loaderTypes}, and returns a normalized
     * version of the type.
     *
     * @param  string $type
     * @return string
     * @throws Zend_Http_UserAgent_Exception on invalid type
     */
    protected function _validateLoaderType($type)
    {
        $type = strtolower($type);
        if (!in_array($type, $this->_loaderTypes)) {
            $types = implode(', ', $this->_loaderTypes);

            require_once 'Zend/Http/UserAgent/Exception.php';
            throw new Zend_Http_UserAgent_Exception(sprintf(
                'Expected one of "%s" for plugin loader type; received "%s"',
                $types,
                (string) $type
            ));
        }
        return $type;
    }

    /**
     * Run the identification sequence to match the right browser type according to the
     * user agent
     *
     * @return Zend_Http_UserAgent_Result
     */
    protected function _matchUserAgent()
    {
        $type = self::DEFAULT_BROWSER_TYPE;

        // If we have no identification sequence, just return the default type
        if (empty($this->_config['identification_sequence'])) {
            return $type;
        }

        // Get sequence against which to match
        $sequence = explode(',', $this->_config['identification_sequence']);

        // If a browser type is already configured, push that to the front of the list
        if (null !== ($browserType = $this->getBrowserType())) {
            array_unshift($sequence, $browserType);
        }

        // Append the default browser type to the list if not alread in the list
        if (!in_array($type, $sequence)) {
            $sequence[] = $type;
        }

        // Test each type until we find a match
        foreach ($sequence as $browserType) {
            $browserType = trim($browserType);
            $className   = $this->_getUserAgentDevice($browserType);

            // Attempt to match this device class
            if ($this->_match($className)) {
                $type = $browserType;
                $this->_browserTypeClass[$type] = $className;
                break;
            }
        }

        return $type;
    }

    /**
     * Creates device object instance
     *
     * @return void
     */
    protected function _createDevice()
    {
        $browserType = $this->getBrowserType();
        $classname   = $this->_getUserAgentDevice($browserType);
        $this->_device = new $classname($this->getUserAgent(), $this->getServer(), $this->getConfig());
    }
}


text:
<?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 Client_Exception
 * @version    $Id: Exception.php 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * @see Zend_Http_Exception
 */
require_once 'Zend/Http/Exception.php';

/**
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Client_Exception extends Zend_Http_Exception
{}


text:
<?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 Client_Adapter
 * @version    $Id: Test.php 24309 2011-07-30 02:52:32Z ramon $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * @see Zend_Uri_Http
 */
require_once 'Zend/Uri/Http.php';
/**
 * @see Zend_Http_Response
 */
require_once 'Zend/Http/Response.php';
/**
 * @see Zend_Http_Client_Adapter_Interface
 */
require_once 'Zend/Http/Client/Adapter/Interface.php';

/**
 * A testing-purposes adapter.
 *
 * Should be used to test all components that rely on Zend_Http_Client,
 * without actually performing an HTTP request. You should instantiate this
 * object manually, and then set it as the client's adapter. Then, you can
 * set the expected response using the setResponse() method.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Client_Adapter_Test implements Zend_Http_Client_Adapter_Interface
{
    /**
     * Parameters array
     *
     * @var array
     */
    protected $config = array();

    /**
     * Buffer of responses to be returned by the read() method.  Can be
     * set using setResponse() and addResponse().
     *
     * @var array
     */
    protected $responses = array("HTTP/1.1 400 Bad Request\r\n\r\n");

    /**
     * Current position in the response buffer
     *
     * @var integer
     */
    protected $responseIndex = 0;

    /**
     * Wether or not the next request will fail with an exception
     *
     * @var boolean
     */
    protected $_nextRequestWillFail = false;

    /**
     * Adapter constructor, currently empty. Config is set using setConfig()
     *
     */
    public function __construct()
    { }

    /**
     * Set the nextRequestWillFail flag
     *
     * @param boolean $flag
     * @return Zend_Http_Client_Adapter_Test
     */
    public function setNextRequestWillFail($flag)
    {
        $this->_nextRequestWillFail = (bool) $flag;

        return $this;
    }

    /**
     * Set the configuration array for the adapter
     *
     * @param Zend_Config | array $config
     */
    public function setConfig($config = array())
    {
        if ($config instanceof Zend_Config) {
            $config = $config->toArray();

        } elseif (! is_array($config)) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception(
                'Array or Zend_Config object expected, got ' . gettype($config)
            );
        }

        foreach ($config as $k => $v) {
            $this->config[strtolower($k)] = $v;
        }
    }

    /**
     * Connect to the remote server
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     * @param int     $timeout
     * @throws Zend_Http_Client_Adapter_Exception
     */
    public function connect($host, $port = 80, $secure = false)
    {
        if ($this->_nextRequestWillFail) {
            $this->_nextRequestWillFail = false;
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception('Request failed');
        }
    }

    /**
     * Send request to the remote server
     *
     * @param string        $method
     * @param Zend_Uri_Http $uri
     * @param string        $http_ver
     * @param array         $headers
     * @param string        $body
     * @return string Request as string
     */
    public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
    {
        $host = $uri->getHost();
            $host = (strtolower($uri->getScheme()) == 'https' ? 'sslv2://' . $host : $host);

        // Build request headers
        $path = $uri->getPath();
        if ($uri->getQuery()) $path .= '?' . $uri->getQuery();
        $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
        foreach ($headers as $k => $v) {
            if (is_string($k)) $v = ucfirst($k) . ": $v";
            $request .= "$v\r\n";
        }

        // Add the request body
        $request .= "\r\n" . $body;

        // Do nothing - just return the request as string

        return $request;
    }

    /**
     * Return the response set in $this->setResponse()
     *
     * @return string
     */
    public function read()
    {
        if ($this->responseIndex >= count($this->responses)) {
            $this->responseIndex = 0;
        }
        return $this->responses[$this->responseIndex++];
    }

    /**
     * Close the connection (dummy)
     *
     */
    public function close()
    { }

    /**
     * Set the HTTP response(s) to be returned by this adapter
     *
     * @param Zend_Http_Response|array|string $response
     */
    public function setResponse($response)
    {
        if ($response instanceof Zend_Http_Response) {
            $response = $response->asString("\r\n");
        }

        $this->responses = (array)$response;
        $this->responseIndex = 0;
    }

    /**
     * Add another response to the response buffer.
     *
     * @param string Zend_Http_Response|$response
     */
    public function addResponse($response)
    {
         if ($response instanceof Zend_Http_Response) {
            $response = $response->asString("\r\n");
        }

        $this->responses[] = $response;
    }

    /**
     * Sets the position of the response buffer.  Selects which
     * response will be returned on the next call to read().
     *
     * @param integer $index
     */
    public function setResponseIndex($index)
    {
        if ($index < 0 || $index >= count($this->responses)) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception(
                'Index out of range of response buffer size');
        }
        $this->responseIndex = $index;
    }

    /**
     * Retrieve the array of all configuration options
     *
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }
}


text:
<?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 Client_Adapter
 * @version    $Id: Stream.php 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * An interface description for Zend_Http_Client_Adapter_Stream classes.
 *
 * This interface decribes Zend_Http_Client_Adapter which supports streaming.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
interface Zend_Http_Client_Adapter_Stream
{
    /**
     * Set output stream
     *
     * This function sets output stream where the result will be stored.
     *
     * @param resource $stream Stream to write the output to
     *
     */
    public function setOutputStream($stream);
}


text:
<?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 Client_Adapter_Exception
 * @version    $Id: Exception.php 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * @see Zend_Http_Client_Exception
 */
require_once 'Zend/Http/Client/Exception.php';

/**
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Client_Adapter_Exception extends Zend_Http_Client_Exception
{
    const READ_TIMEOUT = 1000;
}


text:
<?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 Client_Adapter
 * @version    $Id: Interface.php 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * An interface description for Zend_Http_Client_Adapter classes.
 *
 * These classes are used as connectors for Zend_Http_Client, performing the
 * tasks of connecting, writing, reading and closing connection to the server.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
interface Zend_Http_Client_Adapter_Interface
{
    /**
     * Set the configuration array for the adapter
     *
     * @param array $config
     */
    public function setConfig($config = array());

    /**
     * Connect to the remote server
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     */
    public function connect($host, $port = 80, $secure = false);

    /**
     * Send request to the remote server
     *
     * @param string        $method
     * @param Zend_Uri_Http $url
     * @param string        $http_ver
     * @param array         $headers
     * @param string        $body
     * @return string Request as text
     */
    public function write($method, $url, $http_ver = '1.1', $headers = array(), $body = '');

    /**
     * Read response from server
     *
     * @return string
     */
    public function read();

    /**
     * Close the connection to the server
     *
     */
    public function close();
}


text:
<?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 Client_Adapter
 * @version    $Id: Proxy.php 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * @see Zend_Uri_Http
 */
require_once 'Zend/Uri/Http.php';
/**
 * @see Zend_Http_Client
 */
require_once 'Zend/Http/Client.php';
/**
 * @see Zend_Http_Client_Adapter_Socket
 */
require_once 'Zend/Http/Client/Adapter/Socket.php';

/**
 * HTTP Proxy-supporting Zend_Http_Client adapter class, based on the default
 * socket based adapter.
 *
 * Should be used if proxy HTTP access is required. If no proxy is set, will
 * fall back to Zend_Http_Client_Adapter_Socket behavior. Just like the
 * default Socket adapter, this adapter does not require any special extensions
 * installed.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket
{
    /**
     * Parameters array
     *
     * @var array
     */
    protected $config = array(
        'ssltransport'  => 'ssl',
        'sslcert'       => null,
        'sslpassphrase' => null,
        'sslusecontext' => false,
        'proxy_host'    => '',
        'proxy_port'    => 8080,
        'proxy_user'    => '',
        'proxy_pass'    => '',
        'proxy_auth'    => Zend_Http_Client::AUTH_BASIC,
        'persistent'    => false
    );

    /**
     * Whether HTTPS CONNECT was already negotiated with the proxy or not
     *
     * @var boolean
     */
    protected $negotiated = false;

    /**
     * Connect to the remote server
     *
     * Will try to connect to the proxy server. If no proxy was set, will
     * fall back to the target server (behave like regular Socket adapter)
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     */
    public function connect($host, $port = 80, $secure = false)
    {
        // If no proxy is set, fall back to Socket adapter
        if (! $this->config['proxy_host']) {
            return parent::connect($host, $port, $secure);
        }

        /* Url might require stream context even if proxy connection doesn't */
        if ($secure) {
            $this->config['sslusecontext'] = true;
        }

        // Connect (a non-secure connection) to the proxy server
        return parent::connect(
            $this->config['proxy_host'],
            $this->config['proxy_port'],
            false
        );
    }

    /**
     * Send request to the proxy server
     *
     * @param string        $method
     * @param Zend_Uri_Http $uri
     * @param string        $http_ver
     * @param array         $headers
     * @param string        $body
     * @return string Request as string
     */
    public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
    {
        // If no proxy is set, fall back to default Socket adapter
        if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body);

        // Make sure we're properly connected
        if (! $this->socket) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
        }

        $host = $this->config['proxy_host'];
        $port = $this->config['proxy_port'];

        if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong proxy server");
        }

        // Add Proxy-Authorization header
        if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) {
            $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader(
                $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth']
            );
        }

        // if we are proxying HTTPS, preform CONNECT handshake with the proxy
        if ($uri->getScheme() == 'https' && (! $this->negotiated)) {
            $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers);
            $this->negotiated = true;
        }

        // Save request method for later
        $this->method = $method;

        // Build request headers
        if ($this->negotiated) {
            $path = $uri->getPath();
            if ($uri->getQuery()) {
                $path .= '?' . $uri->getQuery();
            }
            $request = "$method $path HTTP/$http_ver\r\n";
        } else {
            $request = "$method $uri HTTP/$http_ver\r\n";
        }

        // Add all headers to the request string
        foreach ($headers as $k => $v) {
            if (is_string($k)) $v = "$k: $v";
            $request .= "$v\r\n";
        }

        if(is_resource($body)) {
            $request .= "\r\n";
        } else {
            // Add the request body
            $request .= "\r\n" . $body;
        }

        // Send the request
        if (! @fwrite($this->socket, $request)) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
        }

        if(is_resource($body)) {
            if(stream_copy_to_stream($body, $this->socket) == 0) {
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
            }
        }

        return $request;
    }

    /**
     * Preform handshaking with HTTPS proxy using CONNECT method
     *
     * @param string  $host
     * @param integer $port
     * @param string  $http_ver
     * @param array   $headers
     */
    protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array())
    {
        $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
                   "Host: " . $this->config['proxy_host'] . "\r\n";

        // Add the user-agent header
        if (isset($this->config['useragent'])) {
            $request .= "User-agent: " . $this->config['useragent'] . "\r\n";
        }

        // If the proxy-authorization header is set, send it to proxy but remove
        // it from headers sent to target host
        if (isset($headers['proxy-authorization'])) {
            $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n";
            unset($headers['proxy-authorization']);
        }

        $request .= "\r\n";

        // Send the request
        if (! @fwrite($this->socket, $request)) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
        }

        // Read response headers only
        $response = '';
        $gotStatus = false;
        while ($line = @fgets($this->socket)) {
            $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
            if ($gotStatus) {
                $response .= $line;
                if (!chop($line)) break;
            }
        }

        // Check that the response from the proxy is 200
        if (Zend_Http_Response::extractCode($response) != 200) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception("Unable to connect to HTTPS proxy. Server response: " . $response);
        }

        // If all is good, switch socket to secure mode. We have to fall back
        // through the different modes
        $modes = array(
            STREAM_CRYPTO_METHOD_TLS_CLIENT,
            STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
            STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
            STREAM_CRYPTO_METHOD_SSLv2_CLIENT
        );

        $success = false;
        foreach($modes as $mode) {
            $success = stream_socket_enable_crypto($this->socket, true, $mode);
            if ($success) break;
        }

        if (! $success) {
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception("Unable to connect to" .
                    " HTTPS server through proxy: could not negotiate secure connection.");
        }
    }

    /**
     * Close the connection to the server
     *
     */
    public function close()
    {
        parent::close();
        $this->negotiated = false;
    }

    /**
     * Destructor: make sure the socket is disconnected
     *
     */
    public function __destruct()
    {
        if ($this->socket) $this->close();
    }
}


text:
<?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 Client_Adapter
 * @version    $Id: Socket.php 23775 2011-03-01 17:25:24Z ralph $
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

/**
 * @see Zend_Uri_Http
 */
require_once 'Zend/Uri/Http.php';
/**
 * @see Zend_Http_Client_Adapter_Interface
 */
require_once 'Zend/Http/Client/Adapter/Interface.php';
/**
 * @see Zend_Http_Client_Adapter_Stream
 */
require_once 'Zend/Http/Client/Adapter/Stream.php';

/**
 * A sockets based (stream_socket_client) adapter class for Zend_Http_Client. Can be used
 * on almost every PHP environment, and does not require any special extensions.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
{
    /**
     * The socket for server connection
     *
     * @var resource|null
     */
    protected $socket = null;

    /**
     * What host/port are we connected to?
     *
     * @var array
     */
    protected $connected_to = array(null, null);

    /**
     * Stream for storing output
     *
     * @var resource
     */
    protected $out_stream = null;

    /**
     * Parameters array
     *
     * @var array
     */
    protected $config = array(
        'persistent'    => false,
        'ssltransport'  => 'ssl',
        'sslcert'       => null,
        'sslpassphrase' => null,
        'sslusecontext' => false
    );

    /**
     * Request method - will be set by write() and might be used by read()
     *
     * @var string
     */
    protected $method = null;

    /**
     * Stream context
     *
     * @var resource
     */
    protected $_context = null;

    /**
     * Adapter constructor, currently empty. Config is set using setConfig()
     *
     */
    public function __construct()
    {
    }

    /**
     * Set the configuration array for the adapter
     *
     * @param Zend_Config | array $config
     */
    public function setConfig($config = array())
    {
        if ($config instanceof Zend_Config) {
            $config = $config->toArray();

        } elseif (! is_array($config)) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception(
                'Array or Zend_Config object expected, got ' . gettype($config)
            );
        }

        foreach ($config as $k => $v) {
            $this->config[strtolower($k)] = $v;
        }
    }

    /**
      * Retrieve the array of all configuration options
      *
      * @return array
      */
     public function getConfig()
     {
         return $this->config;
     }

     /**
     * Set the stream context for the TCP connection to the server
     *
     * Can accept either a pre-existing stream context resource, or an array
     * of stream options, similar to the options array passed to the
     * stream_context_create() PHP function. In such case a new stream context
     * will be created using the passed options.
     *
     * @since  Zend Framework 1.9
     *
     * @param  mixed $context Stream context or array of context options
     * @return Zend_Http_Client_Adapter_Socket
     */
    public function setStreamContext($context)
    {
        if (is_resource($context) && get_resource_type($context) == 'stream-context') {
            $this->_context = $context;

        } elseif (is_array($context)) {
            $this->_context = stream_context_create($context);

        } else {
            // Invalid parameter
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception(
                "Expecting either a stream context resource or array, got " . gettype($context)
            );
        }

        return $this;
    }

    /**
     * Get the stream context for the TCP connection to the server.
     *
     * If no stream context is set, will create a default one.
     *
     * @return resource
     */
    public function getStreamContext()
    {
        if (! $this->_context) {
            $this->_context = stream_context_create();
        }

        return $this->_context;
    }

    /**
     * Connect to the remote server
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     */
    public function connect($host, $port = 80, $secure = false)
    {
        // If the URI should be accessed via SSL, prepend the Hostname with ssl://
        $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host;

        // If we are connected to the wrong host, disconnect first
        if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) {
            if (is_resource($this->socket)) $this->close();
        }

        // Now, if we are not connected, connect
        if (! is_resource($this->socket) || ! $this->config['keepalive']) {
            $context = $this->getStreamContext();
            if ($secure || $this->config['sslusecontext']) {
                if ($this->config['sslcert'] !== null) {
                    if (! stream_context_set_option($context, 'ssl', 'local_cert',
                                                    $this->config['sslcert'])) {
                        require_once 'Zend/Http/Client/Adapter/Exception.php';
                        throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option');
                    }
                }
                if ($this->config['sslpassphrase'] !== null) {
                    if (! stream_context_set_option($context, 'ssl', 'passphrase',
                                                    $this->config['sslpassphrase'])) {
                        require_once 'Zend/Http/Client/Adapter/Exception.php';
                        throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option');
                    }
                }
            }

            $flags = STREAM_CLIENT_CONNECT;
            if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT;

            $this->socket = @stream_socket_client($host . ':' . $port,
                                                  $errno,
                                                  $errstr,
                                                  (int) $this->config['timeout'],
                                                  $flags,
                                                  $context);

            if (! $this->socket) {
                $this->close();
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception(
                    'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr);
            }

            // Set the stream timeout
            if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) {
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout');
            }

            // Update connected_to
            $this->connected_to = array($host, $port);
        }
    }

    /**
     * Send request to the remote server
     *
     * @param string        $method
     * @param Zend_Uri_Http $uri
     * @param string        $http_ver
     * @param array         $headers
     * @param string        $body
     * @return string Request as string
     */
    public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
    {
        // Make sure we're properly connected
        if (! $this->socket) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected');
        }

        $host = $uri->getHost();
        $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host;
        if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host');
        }

        // Save request method for later
        $this->method = $method;

        // Build request headers
        $path = $uri->getPath();
        if ($uri->getQuery()) $path .= '?' . $uri->getQuery();
        $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
        foreach ($headers as $k => $v) {
            if (is_string($k)) $v = ucfirst($k) . ": $v";
            $request .= "$v\r\n";
        }

        if(is_resource($body)) {
            $request .= "\r\n";
        } else {
            // Add the request body
            $request .= "\r\n" . $body;
        }

        // Send the request
        if (! @fwrite($this->socket, $request)) {
            require_once 'Zend/Http/Client/Adapter/Exception.php';
            throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
        }

        if(is_resource($body)) {
            if(stream_copy_to_stream($body, $this->socket) == 0) {
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
            }
        }

        return $request;
    }

    /**
     * Read response from server
     *
     * @return string
     */
    public function read()
    {
        // First, read headers only
        $response = '';
        $gotStatus = false;
        $stream = !empty($this->config['stream']);

        while (($line = @fgets($this->socket)) !== false) {
            $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
            if ($gotStatus) {
                $response .= $line;
                if (rtrim($line) === '') break;
            }
        }

        $this->_checkSocketReadTimeout();

        $statusCode = Zend_Http_Response::extractCode($response);

        // Handle 100 and 101 responses internally by restarting the read again
        if ($statusCode == 100 || $statusCode == 101) return $this->read();

        // Check headers to see what kind of connection / transfer encoding we have
        $headers = Zend_Http_Response::extractHeaders($response);

        /**
         * Responses to HEAD requests and 204 or 304 responses are not expected
         * to have a body - stop reading here
         */
        if ($statusCode == 304 || $statusCode == 204 ||
            $this->method == Zend_Http_Client::HEAD) {

            // Close the connection if requested to do so by the server
            if (isset($headers['connection']) && $headers['connection'] == 'close') {
                $this->close();
            }
            return $response;
        }

        // If we got a 'transfer-encoding: chunked' header
        if (isset($headers['transfer-encoding'])) {

            if (strtolower($headers['transfer-encoding']) == 'chunked') {

                do {
                    $line  = @fgets($this->socket);
                    $this->_checkSocketReadTimeout();

                    $chunk = $line;

                    // Figure out the next chunk size
                    $chunksize = trim($line);
                    if (! ctype_xdigit($chunksize)) {
                        $this->close();
                        require_once 'Zend/Http/Client/Adapter/Exception.php';
                        throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' .
                            $chunksize . '" unable to read chunked body');
                    }

                    // Convert the hexadecimal value to plain integer
                    $chunksize = hexdec($chunksize);

                    // Read next chunk
                    $read_to = ftell($this->socket) + $chunksize;

                    do {
                        $current_pos = ftell($this->socket);
                        if ($current_pos >= $read_to) break;

                        if($this->out_stream) {
                            if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
                              $this->_checkSocketReadTimeout();
                              break;
                             }
                        } else {
                            $line = @fread($this->socket, $read_to - $current_pos);
                            if ($line === false || strlen($line) === 0) {
                                $this->_checkSocketReadTimeout();
                                break;
                            }
                                    $chunk .= $line;
                        }
                    } while (! feof($this->socket));

                    $chunk .= @fgets($this->socket);
                    $this->_checkSocketReadTimeout();

                    if(!$this->out_stream) {
                        $response .= $chunk;
                    }
                } while ($chunksize > 0);
            } else {
                $this->close();
        require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' .
                    $headers['transfer-encoding'] . '" transfer encoding');
            }

            // We automatically decode chunked-messages when writing to a stream
            // this means we have to disallow the Zend_Http_Response to do it again
            if ($this->out_stream) {
                $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response);
            }
        // Else, if we got the content-length header, read this number of bytes
        } elseif (isset($headers['content-length'])) {

            // If we got more than one Content-Length header (see ZF-9404) use
            // the last value sent
            if (is_array($headers['content-length'])) {
                $contentLength = $headers['content-length'][count($headers['content-length']) - 1];
            } else {
                $contentLength = $headers['content-length'];
            }

            $current_pos = ftell($this->socket);
            $chunk = '';

            for ($read_to = $current_pos + $contentLength;
                 $read_to > $current_pos;
                 $current_pos = ftell($this->socket)) {

                 if($this->out_stream) {
                     if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
                          $this->_checkSocketReadTimeout();
                          break;
                     }
                 } else {
                    $chunk = @fread($this->socket, $read_to - $current_pos);
                    if ($chunk === false || strlen($chunk) === 0) {
                        $this->_checkSocketReadTimeout();
                        break;
                    }

                    $response .= $chunk;
                }

                // Break if the connection ended prematurely
                if (feof($this->socket)) break;
            }

        // Fallback: just read the response until EOF
        } else {

            do {
                if($this->out_stream) {
                    if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) {
                          $this->_checkSocketReadTimeout();
                          break;
                     }
                }  else {
                    $buff = @fread($this->socket, 8192);
                    if ($buff === false || strlen($buff) === 0) {
                        $this->_checkSocketReadTimeout();
                        break;
                    } else {
                        $response .= $buff;
                    }
                }

            } while (feof($this->socket) === false);

            $this->close();
        }

        // Close the connection if requested to do so by the server
        if (isset($headers['connection']) && $headers['connection'] == 'close') {
            $this->close();
        }

        return $response;
    }

    /**
     * Close the connection to the server
     *
     */
    public function close()
    {
        if (is_resource($this->socket)) @fclose($this->socket);
        $this->socket = null;
        $this->connected_to = array(null, null);
    }

    /**
     * Check if the socket has timed out - if so close connection and throw
     * an exception
     *
     * @throws Zend_Http_Client_Adapter_Exception with READ_TIMEOUT code
     */
    protected function _checkSocketReadTimeout()
    {
        if ($this->socket) {
            $info = stream_get_meta_data($this->socket);
            $timedout = $info['timed_out'];
            if ($timedout) {
                $this->close();
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception(
                    "Read timed out after {$this->config['timeout']} seconds",
                    Zend_Http_Client_Adapter_Exception::READ_TIMEOUT
                );
            }
        }
    }

    /**
     * Set output stream for the response
     *
     * @param resource $stream
     * @return Zend_Http_Client_Adapter_Socket
     */
    public function setOutputStream($stream)
    {
        $this->out_stream = $stream;
        return $this;
    }

    /**
     * Destructor: make sure the socket is disconnected
     *
     * If we are in persistent TCP mode, will not close the connection
     *
     */
    public function __destruct()
    {
        if (! $this->config['persistent']) {
            if ($this->socket) $this->close();
        }
    }
}