home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / HTTP / Request.php < prev    next >
Encoding:
PHP Script  |  2008-07-02  |  48.3 KB  |  1,485 lines

  1. <?php
  2. /**
  3.  * Class for performing HTTP requests
  4.  *
  5.  * PHP versions 4 and 5
  6.  * 
  7.  * LICENSE:
  8.  *
  9.  * Copyright (c) 2002-2007, Richard Heyes
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or without
  13.  * modification, are permitted provided that the following conditions
  14.  * are met:
  15.  *
  16.  * o Redistributions of source code must retain the above copyright
  17.  *   notice, this list of conditions and the following disclaimer.
  18.  * o Redistributions in binary form must reproduce the above copyright
  19.  *   notice, this list of conditions and the following disclaimer in the
  20.  *   documentation and/or other materials provided with the distribution.
  21.  * o The names of the authors may not be used to endorse or promote
  22.  *   products derived from this software without specific prior written
  23.  *   permission.
  24.  *
  25.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  26.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  27.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  28.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  29.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  30.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  31.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  32.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  33.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  34.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  35.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36.  *
  37.  * @category    HTTP
  38.  * @package     HTTP_Request
  39.  * @author      Richard Heyes <richard@phpguru.org>
  40.  * @author      Alexey Borzov <avb@php.net>
  41.  * @copyright   2002-2007 Richard Heyes
  42.  * @license     http://opensource.org/licenses/bsd-license.php New BSD License
  43.  * @version     CVS: $Id: Request.php,v 1.58 2007/10/26 13:45:56 avb Exp $
  44.  * @link        http://pear.php.net/package/HTTP_Request/ 
  45.  */
  46.  
  47. /**
  48.  * PEAR and PEAR_Error classes (for error handling)
  49.  */
  50. require_once 'PEAR.php';
  51. /**
  52.  * Socket class
  53.  */
  54. require_once 'Net/Socket.php';
  55. /**
  56.  * URL handling class
  57.  */ 
  58. require_once 'Net/URL.php';
  59.  
  60. /**#@+
  61.  * Constants for HTTP request methods
  62.  */ 
  63. define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
  64. define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
  65. define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
  66. define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
  67. define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
  68. define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
  69. define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
  70. /**#@-*/
  71.  
  72. /**#@+
  73.  * Constants for HTTP request error codes
  74.  */ 
  75. define('HTTP_REQUEST_ERROR_FILE',             1);
  76. define('HTTP_REQUEST_ERROR_URL',              2);
  77. define('HTTP_REQUEST_ERROR_PROXY',            4);
  78. define('HTTP_REQUEST_ERROR_REDIRECTS',        8);
  79. define('HTTP_REQUEST_ERROR_RESPONSE',        16);  
  80. define('HTTP_REQUEST_ERROR_GZIP_METHOD',     32);
  81. define('HTTP_REQUEST_ERROR_GZIP_READ',       64);
  82. define('HTTP_REQUEST_ERROR_GZIP_DATA',      128);
  83. define('HTTP_REQUEST_ERROR_GZIP_CRC',       256);
  84. /**#@-*/
  85.  
  86. /**#@+
  87.  * Constants for HTTP protocol versions
  88.  */
  89. define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
  90. define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
  91. /**#@-*/
  92.  
  93. if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
  94.    /**
  95.     * Whether string functions are overloaded by their mbstring equivalents 
  96.     */
  97.     define('HTTP_REQUEST_MBSTRING', true);
  98. } else {
  99.    /**
  100.     * @ignore
  101.     */
  102.     define('HTTP_REQUEST_MBSTRING', false);
  103. }
  104.  
  105. /**
  106.  * Class for performing HTTP requests
  107.  *
  108.  * Simple example (fetches yahoo.com and displays it):
  109.  * <code>
  110.  * $a = &new HTTP_Request('http://www.yahoo.com/');
  111.  * $a->sendRequest();
  112.  * echo $a->getResponseBody();
  113.  * </code>
  114.  *
  115.  * @category    HTTP
  116.  * @package     HTTP_Request
  117.  * @author      Richard Heyes <richard@phpguru.org>
  118.  * @author      Alexey Borzov <avb@php.net>
  119.  * @version     Release: 1.4.2
  120.  */
  121. class HTTP_Request
  122. {
  123.    /**#@+
  124.     * @access private
  125.     */
  126.     /**
  127.     * Instance of Net_URL
  128.     * @var Net_URL
  129.     */
  130.     var $_url;
  131.  
  132.     /**
  133.     * Type of request
  134.     * @var string
  135.     */
  136.     var $_method;
  137.  
  138.     /**
  139.     * HTTP Version
  140.     * @var string
  141.     */
  142.     var $_http;
  143.  
  144.     /**
  145.     * Request headers
  146.     * @var array
  147.     */
  148.     var $_requestHeaders;
  149.  
  150.     /**
  151.     * Basic Auth Username
  152.     * @var string
  153.     */
  154.     var $_user;
  155.     
  156.     /**
  157.     * Basic Auth Password
  158.     * @var string
  159.     */
  160.     var $_pass;
  161.  
  162.     /**
  163.     * Socket object
  164.     * @var Net_Socket
  165.     */
  166.     var $_sock;
  167.     
  168.     /**
  169.     * Proxy server
  170.     * @var string
  171.     */
  172.     var $_proxy_host;
  173.     
  174.     /**
  175.     * Proxy port
  176.     * @var integer
  177.     */
  178.     var $_proxy_port;
  179.     
  180.     /**
  181.     * Proxy username
  182.     * @var string
  183.     */
  184.     var $_proxy_user;
  185.     
  186.     /**
  187.     * Proxy password
  188.     * @var string
  189.     */
  190.     var $_proxy_pass;
  191.  
  192.     /**
  193.     * Post data
  194.     * @var array
  195.     */
  196.     var $_postData;
  197.  
  198.    /**
  199.     * Request body  
  200.     * @var string
  201.     */
  202.     var $_body;
  203.  
  204.    /**
  205.     * A list of methods that MUST NOT have a request body, per RFC 2616
  206.     * @var array
  207.     */
  208.     var $_bodyDisallowed = array('TRACE');
  209.  
  210.    /**
  211.     * Files to post 
  212.     * @var array
  213.     */
  214.     var $_postFiles = array();
  215.  
  216.     /**
  217.     * Connection timeout.
  218.     * @var float
  219.     */
  220.     var $_timeout;
  221.     
  222.     /**
  223.     * HTTP_Response object
  224.     * @var HTTP_Response
  225.     */
  226.     var $_response;
  227.     
  228.     /**
  229.     * Whether to allow redirects
  230.     * @var boolean
  231.     */
  232.     var $_allowRedirects;
  233.     
  234.     /**
  235.     * Maximum redirects allowed
  236.     * @var integer
  237.     */
  238.     var $_maxRedirects;
  239.     
  240.     /**
  241.     * Current number of redirects
  242.     * @var integer
  243.     */
  244.     var $_redirects;
  245.  
  246.    /**
  247.     * Whether to append brackets [] to array variables
  248.     * @var bool
  249.     */
  250.     var $_useBrackets = true;
  251.  
  252.    /**
  253.     * Attached listeners
  254.     * @var array
  255.     */
  256.     var $_listeners = array();
  257.  
  258.    /**
  259.     * Whether to save response body in response object property  
  260.     * @var bool
  261.     */
  262.     var $_saveBody = true;
  263.  
  264.    /**
  265.     * Timeout for reading from socket (array(seconds, microseconds))
  266.     * @var array
  267.     */
  268.     var $_readTimeout = null;
  269.  
  270.    /**
  271.     * Options to pass to Net_Socket::connect. See stream_context_create
  272.     * @var array
  273.     */
  274.     var $_socketOptions = null;
  275.    /**#@-*/
  276.  
  277.     /**
  278.     * Constructor
  279.     *
  280.     * Sets up the object
  281.     * @param    string  The url to fetch/access
  282.     * @param    array   Associative array of parameters which can have the following keys:
  283.     * <ul>
  284.     *   <li>method         - Method to use, GET, POST etc (string)</li>
  285.     *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>
  286.     *   <li>user           - Basic Auth username (string)</li>
  287.     *   <li>pass           - Basic Auth password (string)</li>
  288.     *   <li>proxy_host     - Proxy server host (string)</li>
  289.     *   <li>proxy_port     - Proxy server port (integer)</li>
  290.     *   <li>proxy_user     - Proxy auth username (string)</li>
  291.     *   <li>proxy_pass     - Proxy auth password (string)</li>
  292.     *   <li>timeout        - Connection timeout in seconds (float)</li>
  293.     *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>
  294.     *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>
  295.     *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>
  296.     *   <li>saveBody       - Whether to save response body in response object property (bool)</li>
  297.     *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
  298.     *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>
  299.     * </ul>
  300.     * @access public
  301.     */
  302.     function HTTP_Request($url = '', $params = array())
  303.     {
  304.         $this->_method         =  HTTP_REQUEST_METHOD_GET;
  305.         $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
  306.         $this->_requestHeaders = array();
  307.         $this->_postData       = array();
  308.         $this->_body           = null;
  309.  
  310.         $this->_user = null;
  311.         $this->_pass = null;
  312.  
  313.         $this->_proxy_host = null;
  314.         $this->_proxy_port = null;
  315.         $this->_proxy_user = null;
  316.         $this->_proxy_pass = null;
  317.  
  318.         $this->_allowRedirects = false;
  319.         $this->_maxRedirects   = 3;
  320.         $this->_redirects      = 0;
  321.  
  322.         $this->_timeout  = null;
  323.         $this->_response = null;
  324.  
  325.         foreach ($params as $key => $value) {
  326.             $this->{'_' . $key} = $value;
  327.         }
  328.  
  329.         if (!empty($url)) {
  330.             $this->setURL($url);
  331.         }
  332.  
  333.         // Default useragent
  334.         $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
  335.  
  336.         // We don't do keep-alives by default
  337.         $this->addHeader('Connection', 'close');
  338.  
  339.         // Basic authentication
  340.         if (!empty($this->_user)) {
  341.             $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
  342.         }
  343.  
  344.         // Proxy authentication (see bug #5913)
  345.         if (!empty($this->_proxy_user)) {
  346.             $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
  347.         }
  348.  
  349.         // Use gzip encoding if possible
  350.         if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
  351.             $this->addHeader('Accept-Encoding', 'gzip');
  352.         }
  353.     }
  354.     
  355.     /**
  356.     * Generates a Host header for HTTP/1.1 requests
  357.     *
  358.     * @access private
  359.     * @return string
  360.     */
  361.     function _generateHostHeader()
  362.     {
  363.         if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
  364.             $host = $this->_url->host . ':' . $this->_url->port;
  365.  
  366.         } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
  367.             $host = $this->_url->host . ':' . $this->_url->port;
  368.  
  369.         } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
  370.             $host = $this->_url->host . ':' . $this->_url->port;
  371.         
  372.         } else {
  373.             $host = $this->_url->host;
  374.         }
  375.  
  376.         return $host;
  377.     }
  378.     
  379.     /**
  380.     * Resets the object to its initial state (DEPRECATED).
  381.     * Takes the same parameters as the constructor.
  382.     *
  383.     * @param  string $url    The url to be requested
  384.     * @param  array  $params Associative array of parameters
  385.     *                        (see constructor for details)
  386.     * @access public
  387.     * @deprecated deprecated since 1.2, call the constructor if this is necessary
  388.     */
  389.     function reset($url, $params = array())
  390.     {
  391.         $this->HTTP_Request($url, $params);
  392.     }
  393.  
  394.     /**
  395.     * Sets the URL to be requested
  396.     *
  397.     * @param  string The url to be requested
  398.     * @access public
  399.     */
  400.     function setURL($url)
  401.     {
  402.         $this->_url = &new Net_URL($url, $this->_useBrackets);
  403.  
  404.         if (!empty($this->_url->user) || !empty($this->_url->pass)) {
  405.             $this->setBasicAuth($this->_url->user, $this->_url->pass);
  406.         }
  407.  
  408.         if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
  409.             $this->addHeader('Host', $this->_generateHostHeader());
  410.         }
  411.  
  412.         // set '/' instead of empty path rather than check later (see bug #8662)
  413.         if (empty($this->_url->path)) {
  414.             $this->_url->path = '/';
  415.         } 
  416.     }
  417.     
  418.    /**
  419.     * Returns the current request URL  
  420.     *
  421.     * @return   string  Current request URL
  422.     * @access   public
  423.     */
  424.     function getUrl()
  425.     {
  426.         return empty($this->_url)? '': $this->_url->getUrl();
  427.     }
  428.  
  429.     /**
  430.     * Sets a proxy to be used
  431.     *
  432.     * @param string     Proxy host
  433.     * @param int        Proxy port
  434.     * @param string     Proxy username
  435.     * @param string     Proxy password
  436.     * @access public
  437.     */
  438.     function setProxy($host, $port = 8080, $user = null, $pass = null)
  439.     {
  440.         $this->_proxy_host = $host;
  441.         $this->_proxy_port = $port;
  442.         $this->_proxy_user = $user;
  443.         $this->_proxy_pass = $pass;
  444.  
  445.         if (!empty($user)) {
  446.             $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  447.         }
  448.     }
  449.  
  450.     /**
  451.     * Sets basic authentication parameters
  452.     *
  453.     * @param string     Username
  454.     * @param string     Password
  455.     */
  456.     function setBasicAuth($user, $pass)
  457.     {
  458.         $this->_user = $user;
  459.         $this->_pass = $pass;
  460.  
  461.         $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  462.     }
  463.  
  464.     /**
  465.     * Sets the method to be used, GET, POST etc.
  466.     *
  467.     * @param string     Method to use. Use the defined constants for this
  468.     * @access public
  469.     */
  470.     function setMethod($method)
  471.     {
  472.         $this->_method = $method;
  473.     }
  474.  
  475.     /**
  476.     * Sets the HTTP version to use, 1.0 or 1.1
  477.     *
  478.     * @param string     Version to use. Use the defined constants for this
  479.     * @access public
  480.     */
  481.     function setHttpVer($http)
  482.     {
  483.         $this->_http = $http;
  484.     }
  485.  
  486.     /**
  487.     * Adds a request header
  488.     *
  489.     * @param string     Header name
  490.     * @param string     Header value
  491.     * @access public
  492.     */
  493.     function addHeader($name, $value)
  494.     {
  495.         $this->_requestHeaders[strtolower($name)] = $value;
  496.     }
  497.  
  498.     /**
  499.     * Removes a request header
  500.     *
  501.     * @param string     Header name to remove
  502.     * @access public
  503.     */
  504.     function removeHeader($name)
  505.     {
  506.         if (isset($this->_requestHeaders[strtolower($name)])) {
  507.             unset($this->_requestHeaders[strtolower($name)]);
  508.         }
  509.     }
  510.  
  511.     /**
  512.     * Adds a querystring parameter
  513.     *
  514.     * @param string     Querystring parameter name
  515.     * @param string     Querystring parameter value
  516.     * @param bool       Whether the value is already urlencoded or not, default = not
  517.     * @access public
  518.     */
  519.     function addQueryString($name, $value, $preencoded = false)
  520.     {
  521.         $this->_url->addQueryString($name, $value, $preencoded);
  522.     }    
  523.     
  524.     /**
  525.     * Sets the querystring to literally what you supply
  526.     *
  527.     * @param string     The querystring data. Should be of the format foo=bar&x=y etc
  528.     * @param bool       Whether data is already urlencoded or not, default = already encoded
  529.     * @access public
  530.     */
  531.     function addRawQueryString($querystring, $preencoded = true)
  532.     {
  533.         $this->_url->addRawQueryString($querystring, $preencoded);
  534.     }
  535.  
  536.     /**
  537.     * Adds postdata items
  538.     *
  539.     * @param string     Post data name
  540.     * @param string     Post data value
  541.     * @param bool       Whether data is already urlencoded or not, default = not
  542.     * @access public
  543.     */
  544.     function addPostData($name, $value, $preencoded = false)
  545.     {
  546.         if ($preencoded) {
  547.             $this->_postData[$name] = $value;
  548.         } else {
  549.             $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
  550.         }
  551.     }
  552.  
  553.    /**
  554.     * Recursively applies the callback function to the value
  555.     * 
  556.     * @param    mixed   Callback function
  557.     * @param    mixed   Value to process
  558.     * @access   private
  559.     * @return   mixed   Processed value
  560.     */
  561.     function _arrayMapRecursive($callback, $value)
  562.     {
  563.         if (!is_array($value)) {
  564.             return call_user_func($callback, $value);
  565.         } else {
  566.             $map = array();
  567.             foreach ($value as $k => $v) {
  568.                 $map[$k] = $this->_arrayMapRecursive($callback, $v);
  569.             }
  570.             return $map;
  571.         }
  572.     }
  573.  
  574.    /**
  575.     * Adds a file to upload
  576.     * 
  577.     * This also changes content-type to 'multipart/form-data' for proper upload
  578.     * 
  579.     * @access public
  580.     * @param  string    name of file-upload field
  581.     * @param  mixed     file name(s)
  582.     * @param  mixed     content-type(s) of file(s) being uploaded
  583.     * @return bool      true on success
  584.     * @throws PEAR_Error
  585.     */
  586.     function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
  587.     {
  588.         if (!is_array($fileName) && !is_readable($fileName)) {
  589.             return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
  590.         } elseif (is_array($fileName)) {
  591.             foreach ($fileName as $name) {
  592.                 if (!is_readable($name)) {
  593.                     return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
  594.                 }
  595.             }
  596.         }
  597.         $this->addHeader('Content-Type', 'multipart/form-data');
  598.         $this->_postFiles[$inputName] = array(
  599.             'name' => $fileName,
  600.             'type' => $contentType
  601.         );
  602.         return true;
  603.     }
  604.  
  605.     /**
  606.     * Adds raw postdata (DEPRECATED)
  607.     *
  608.     * @param string     The data
  609.     * @param bool       Whether data is preencoded or not, default = already encoded
  610.     * @access public
  611.     * @deprecated       deprecated since 1.3.0, method setBody() should be used instead
  612.     */
  613.     function addRawPostData($postdata, $preencoded = true)
  614.     {
  615.         $this->_body = $preencoded ? $postdata : urlencode($postdata);
  616.     }
  617.  
  618.    /**
  619.     * Sets the request body (for POST, PUT and similar requests)
  620.     *
  621.     * @param    string  Request body
  622.     * @access   public
  623.     */
  624.     function setBody($body)
  625.     {
  626.         $this->_body = $body;
  627.     }
  628.  
  629.     /**
  630.     * Clears any postdata that has been added (DEPRECATED). 
  631.     * 
  632.     * Useful for multiple request scenarios.
  633.     *
  634.     * @access public
  635.     * @deprecated deprecated since 1.2
  636.     */
  637.     function clearPostData()
  638.     {
  639.         $this->_postData = null;
  640.     }
  641.  
  642.     /**
  643.     * Appends a cookie to "Cookie:" header
  644.     * 
  645.     * @param string $name cookie name
  646.     * @param string $value cookie value
  647.     * @access public
  648.     */
  649.     function addCookie($name, $value)
  650.     {
  651.         $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
  652.         $this->addHeader('Cookie', $cookies . $name . '=' . $value);
  653.     }
  654.     
  655.     /**
  656.     * Clears any cookies that have been added (DEPRECATED). 
  657.     * 
  658.     * Useful for multiple request scenarios
  659.     *
  660.     * @access public
  661.     * @deprecated deprecated since 1.2
  662.     */
  663.     function clearCookies()
  664.     {
  665.         $this->removeHeader('Cookie');
  666.     }
  667.  
  668.     /**
  669.     * Sends the request
  670.     *
  671.     * @access public
  672.     * @param  bool   Whether to store response body in Response object property,
  673.     *                set this to false if downloading a LARGE file and using a Listener
  674.     * @return mixed  PEAR error on error, true otherwise
  675.     */
  676.     function sendRequest($saveBody = true)
  677.     {
  678.         if (!is_a($this->_url, 'Net_URL')) {
  679.             return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
  680.         }
  681.  
  682.         $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
  683.         $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
  684.  
  685.         // 4.3.0 supports SSL connections using OpenSSL. The function test determines
  686.         // we running on at least 4.3.0
  687.         if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) {
  688.             if (isset($this->_proxy_host)) {
  689.                 return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
  690.             }
  691.             $host = 'ssl://' . $host;
  692.         }
  693.  
  694.         // magic quotes may fuck up file uploads and chunked response processing
  695.         $magicQuotes = ini_get('magic_quotes_runtime');
  696.         ini_set('magic_quotes_runtime', false);
  697.  
  698.         // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive 
  699.         // connection token to a proxy server...
  700.         if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
  701.             'Keep-Alive' == $this->_requestHeaders['connection'])
  702.         {
  703.             $this->removeHeader('connection');
  704.         }
  705.  
  706.         $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
  707.                      (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
  708.         $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
  709.         $sockKey   = $host . ':' . $port;
  710.         unset($this->_sock);
  711.  
  712.         // There is a connected socket in the "static" property?
  713.         if ($keepAlive && !empty($sockets[$sockKey]) &&
  714.             !empty($sockets[$sockKey]->fp)) 
  715.         {
  716.             $this->_sock =& $sockets[$sockKey];
  717.             $err = null;
  718.         } else {
  719.             $this->_notify('connect');
  720.             $this->_sock =& new Net_Socket();
  721.             $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
  722.         }
  723.         PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
  724.  
  725.         if (!PEAR::isError($err)) {
  726.             if (!empty($this->_readTimeout)) {
  727.                 $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
  728.             }
  729.  
  730.             $this->_notify('sentRequest');
  731.  
  732.             // Read the response
  733.             $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
  734.             $err = $this->_response->process(
  735.                 $this->_saveBody && $saveBody,
  736.                 HTTP_REQUEST_METHOD_HEAD != $this->_method
  737.             );
  738.  
  739.             if ($keepAlive) {
  740.                 $keepAlive = (isset($this->_response->_headers['content-length'])
  741.                               || (isset($this->_response->_headers['transfer-encoding'])
  742.                                   && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
  743.                 if ($keepAlive) {
  744.                     if (isset($this->_response->_headers['connection'])) {
  745.                         $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
  746.                     } else {
  747.                         $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
  748.                     }
  749.                 }
  750.             }
  751.         }
  752.  
  753.         ini_set('magic_quotes_runtime', $magicQuotes);
  754.  
  755.         if (PEAR::isError($err)) {
  756.             return $err;
  757.         }
  758.  
  759.         if (!$keepAlive) {
  760.             $this->disconnect();
  761.         // Store the connected socket in "static" property
  762.         } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
  763.             $sockets[$sockKey] =& $this->_sock;
  764.         }
  765.  
  766.         // Check for redirection
  767.         if (    $this->_allowRedirects
  768.             AND $this->_redirects <= $this->_maxRedirects
  769.             AND $this->getResponseCode() > 300
  770.             AND $this->getResponseCode() < 399
  771.             AND !empty($this->_response->_headers['location'])) {
  772.  
  773.             
  774.             $redirect = $this->_response->_headers['location'];
  775.  
  776.             // Absolute URL
  777.             if (preg_match('/^https?:\/\//i', $redirect)) {
  778.                 $this->_url = &new Net_URL($redirect);
  779.                 $this->addHeader('Host', $this->_generateHostHeader());
  780.             // Absolute path
  781.             } elseif ($redirect{0} == '/') {
  782.                 $this->_url->path = $redirect;
  783.             
  784.             // Relative path
  785.             } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
  786.                 if (substr($this->_url->path, -1) == '/') {
  787.                     $redirect = $this->_url->path . $redirect;
  788.                 } else {
  789.                     $redirect = dirname($this->_url->path) . '/' . $redirect;
  790.                 }
  791.                 $redirect = Net_URL::resolvePath($redirect);
  792.                 $this->_url->path = $redirect;
  793.                 
  794.             // Filename, no path
  795.             } else {
  796.                 if (substr($this->_url->path, -1) == '/') {
  797.                     $redirect = $this->_url->path . $redirect;
  798.                 } else {
  799.                     $redirect = dirname($this->_url->path) . '/' . $redirect;
  800.                 }
  801.                 $this->_url->path = $redirect;
  802.             }
  803.  
  804.             $this->_redirects++;
  805.             return $this->sendRequest($saveBody);
  806.  
  807.         // Too many redirects
  808.         } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
  809.             return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
  810.         }
  811.  
  812.         return true;
  813.     }
  814.  
  815.     /**
  816.      * Disconnect the socket, if connected. Only useful if using Keep-Alive.
  817.      *
  818.      * @access public
  819.      */
  820.     function disconnect()
  821.     {
  822.         if (!empty($this->_sock) && !empty($this->_sock->fp)) {
  823.             $this->_notify('disconnect');
  824.             $this->_sock->disconnect();
  825.         }
  826.     }
  827.  
  828.     /**
  829.     * Returns the response code
  830.     *
  831.     * @access public
  832.     * @return mixed     Response code, false if not set
  833.     */
  834.     function getResponseCode()
  835.     {
  836.         return isset($this->_response->_code) ? $this->_response->_code : false;
  837.     }
  838.  
  839.     /**
  840.     * Returns either the named header or all if no name given
  841.     *
  842.     * @access public
  843.     * @param string     The header name to return, do not set to get all headers
  844.     * @return mixed     either the value of $headername (false if header is not present)
  845.     *                   or an array of all headers
  846.     */
  847.     function getResponseHeader($headername = null)
  848.     {
  849.         if (!isset($headername)) {
  850.             return isset($this->_response->_headers)? $this->_response->_headers: array();
  851.         } else {
  852.             $headername = strtolower($headername);
  853.             return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
  854.         }
  855.     }
  856.  
  857.     /**
  858.     * Returns the body of the response
  859.     *
  860.     * @access public
  861.     * @return mixed     response body, false if not set
  862.     */
  863.     function getResponseBody()
  864.     {
  865.         return isset($this->_response->_body) ? $this->_response->_body : false;
  866.     }
  867.  
  868.     /**
  869.     * Returns cookies set in response
  870.     * 
  871.     * @access public
  872.     * @return mixed     array of response cookies, false if none are present
  873.     */
  874.     function getResponseCookies()
  875.     {
  876.         return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
  877.     }
  878.  
  879.     /**
  880.     * Builds the request string
  881.     *
  882.     * @access private
  883.     * @return string The request string
  884.     */
  885.     function _buildRequest()
  886.     {
  887.         $separator = ini_get('arg_separator.output');
  888.         ini_set('arg_separator.output', '&');
  889.         $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
  890.         ini_set('arg_separator.output', $separator);
  891.  
  892.         $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
  893.         $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
  894.         $path = $this->_url->path . $querystring;
  895.         $url  = $host . $port . $path;
  896.  
  897.         if (!strlen($url)) {
  898.             $url = '/';
  899.         }
  900.  
  901.         $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
  902.  
  903.         if (in_array($this->_method, $this->_bodyDisallowed) ||
  904.             (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
  905.              (empty($this->_postData) && empty($this->_postFiles)))))
  906.         {
  907.             $this->removeHeader('Content-Type');
  908.         } else {
  909.             if (empty($this->_requestHeaders['content-type'])) {
  910.                 // Add default content-type
  911.                 $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
  912.             } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
  913.                 $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
  914.                 $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
  915.             }
  916.         }
  917.  
  918.         // Request Headers
  919.         if (!empty($this->_requestHeaders)) {
  920.             foreach ($this->_requestHeaders as $name => $value) {
  921.                 $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
  922.                 $request      .= $canonicalName . ': ' . $value . "\r\n";
  923.             }
  924.         }
  925.  
  926.         // No post data or wrong method, so simply add a final CRLF
  927.         if (in_array($this->_method, $this->_bodyDisallowed) || 
  928.             (HTTP_REQUEST_METHOD_POST != $this->_method && 0 == strlen($this->_body))) {
  929.  
  930.             $request .= "\r\n";
  931.  
  932.         // Post data if it's an array
  933.         } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && 
  934.                   (!empty($this->_postData) || !empty($this->_postFiles))) {
  935.  
  936.             // "normal" POST request
  937.             if (!isset($boundary)) {
  938.                 $postdata = implode('&', array_map(
  939.                     create_function('$a', 'return $a[0] . \'=\' . $a[1];'), 
  940.                     $this->_flattenArray('', $this->_postData)
  941.                 ));
  942.  
  943.             // multipart request, probably with file uploads
  944.             } else {
  945.                 $postdata = '';
  946.                 if (!empty($this->_postData)) {
  947.                     $flatData = $this->_flattenArray('', $this->_postData);
  948.                     foreach ($flatData as $item) {
  949.                         $postdata .= '--' . $boundary . "\r\n";
  950.                         $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
  951.                         $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
  952.                     }
  953.                 }
  954.                 foreach ($this->_postFiles as $name => $value) {
  955.                     if (is_array($value['name'])) {
  956.                         $varname       = $name . ($this->_useBrackets? '[]': '');
  957.                     } else {
  958.                         $varname       = $name;
  959.                         $value['name'] = array($value['name']);
  960.                     }
  961.                     foreach ($value['name'] as $key => $filename) {
  962.                         $fp   = fopen($filename, 'r');
  963.                         $data = fread($fp, filesize($filename));
  964.                         fclose($fp);
  965.                         $basename = basename($filename);
  966.                         $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
  967.  
  968.                         $postdata .= '--' . $boundary . "\r\n";
  969.                         $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
  970.                         $postdata .= "\r\nContent-Type: " . $type;
  971.                         $postdata .= "\r\n\r\n" . $data . "\r\n";
  972.                     }
  973.                 }
  974.                 $postdata .= '--' . $boundary . "--\r\n";
  975.             }
  976.             $request .= 'Content-Length: ' .
  977.                         (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
  978.                         "\r\n\r\n";
  979.             $request .= $postdata;
  980.  
  981.         // Explicitly set request body
  982.         } elseif (0 < strlen($this->_body)) {
  983.  
  984.             $request .= 'Content-Length: ' .
  985.                         (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
  986.                         "\r\n\r\n";
  987.             $request .= $this->_body;
  988.  
  989.         // Terminate headers with CRLF on POST request with no body, too
  990.         } else {
  991.  
  992.             $request .= "\r\n";
  993.         }
  994.         
  995.         return $request;
  996.     }
  997.  
  998.    /**
  999.     * Helper function to change the (probably multidimensional) associative array
  1000.     * into the simple one.
  1001.     *
  1002.     * @param    string  name for item
  1003.     * @param    mixed   item's values
  1004.     * @return   array   array with the following items: array('item name', 'item value');
  1005.     * @access   private
  1006.     */
  1007.     function _flattenArray($name, $values)
  1008.     {
  1009.         if (!is_array($values)) {
  1010.             return array(array($name, $values));
  1011.         } else {
  1012.             $ret = array();
  1013.             foreach ($values as $k => $v) {
  1014.                 if (empty($name)) {
  1015.                     $newName = $k;
  1016.                 } elseif ($this->_useBrackets) {
  1017.                     $newName = $name . '[' . $k . ']';
  1018.                 } else {
  1019.                     $newName = $name;
  1020.                 }
  1021.                 $ret = array_merge($ret, $this->_flattenArray($newName, $v));
  1022.             }
  1023.             return $ret;
  1024.         }
  1025.     }
  1026.  
  1027.  
  1028.    /**
  1029.     * Adds a Listener to the list of listeners that are notified of
  1030.     * the object's events
  1031.     * 
  1032.     * Events sent by HTTP_Request object
  1033.     * - 'connect': on connection to server
  1034.     * - 'sentRequest': after the request was sent
  1035.     * - 'disconnect': on disconnection from server
  1036.     *
  1037.     * Events sent by HTTP_Response object
  1038.     * - 'gotHeaders': after receiving response headers (headers are passed in $data)
  1039.     * - 'tick': on receiving a part of response body (the part is passed in $data)
  1040.     * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
  1041.     * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
  1042.     *
  1043.     * @param    HTTP_Request_Listener   listener to attach
  1044.     * @return   boolean                 whether the listener was successfully attached
  1045.     * @access   public
  1046.     */
  1047.     function attach(&$listener)
  1048.     {
  1049.         if (!is_a($listener, 'HTTP_Request_Listener')) {
  1050.             return false;
  1051.         }
  1052.         $this->_listeners[$listener->getId()] =& $listener;
  1053.         return true;
  1054.     }
  1055.  
  1056.  
  1057.    /**
  1058.     * Removes a Listener from the list of listeners 
  1059.     * 
  1060.     * @param    HTTP_Request_Listener   listener to detach
  1061.     * @return   boolean                 whether the listener was successfully detached
  1062.     * @access   public
  1063.     */
  1064.     function detach(&$listener)
  1065.     {
  1066.         if (!is_a($listener, 'HTTP_Request_Listener') || 
  1067.             !isset($this->_listeners[$listener->getId()])) {
  1068.             return false;
  1069.         }
  1070.         unset($this->_listeners[$listener->getId()]);
  1071.         return true;
  1072.     }
  1073.  
  1074.  
  1075.    /**
  1076.     * Notifies all registered listeners of an event.
  1077.     * 
  1078.     * @param    string  Event name
  1079.     * @param    mixed   Additional data
  1080.     * @access   private
  1081.     * @see      HTTP_Request::attach()
  1082.     */
  1083.     function _notify($event, $data = null)
  1084.     {
  1085.         foreach (array_keys($this->_listeners) as $id) {
  1086.             $this->_listeners[$id]->update($this, $event, $data);
  1087.         }
  1088.     }
  1089. }
  1090.  
  1091.  
  1092. /**
  1093.  * Response class to complement the Request class
  1094.  *
  1095.  * @category    HTTP
  1096.  * @package     HTTP_Request
  1097.  * @author      Richard Heyes <richard@phpguru.org>
  1098.  * @author      Alexey Borzov <avb@php.net>
  1099.  * @version     Release: 1.4.2
  1100.  */
  1101. class HTTP_Response
  1102. {
  1103.     /**
  1104.     * Socket object
  1105.     * @var Net_Socket
  1106.     */
  1107.     var $_sock;
  1108.  
  1109.     /**
  1110.     * Protocol
  1111.     * @var string
  1112.     */
  1113.     var $_protocol;
  1114.     
  1115.     /**
  1116.     * Return code
  1117.     * @var string
  1118.     */
  1119.     var $_code;
  1120.     
  1121.     /**
  1122.     * Response headers
  1123.     * @var array
  1124.     */
  1125.     var $_headers;
  1126.  
  1127.     /**
  1128.     * Cookies set in response  
  1129.     * @var array
  1130.     */
  1131.     var $_cookies;
  1132.  
  1133.     /**
  1134.     * Response body
  1135.     * @var string
  1136.     */
  1137.     var $_body = '';
  1138.  
  1139.    /**
  1140.     * Used by _readChunked(): remaining length of the current chunk
  1141.     * @var string
  1142.     */
  1143.     var $_chunkLength = 0;
  1144.  
  1145.    /**
  1146.     * Attached listeners
  1147.     * @var array
  1148.     */
  1149.     var $_listeners = array();
  1150.  
  1151.    /**
  1152.     * Bytes left to read from message-body
  1153.     * @var null|int
  1154.     */
  1155.     var $_toRead;
  1156.  
  1157.     /**
  1158.     * Constructor
  1159.     *
  1160.     * @param  Net_Socket    socket to read the response from
  1161.     * @param  array         listeners attached to request
  1162.     */
  1163.     function HTTP_Response(&$sock, &$listeners)
  1164.     {
  1165.         $this->_sock      =& $sock;
  1166.         $this->_listeners =& $listeners;
  1167.     }
  1168.  
  1169.  
  1170.    /**
  1171.     * Processes a HTTP response
  1172.     * 
  1173.     * This extracts response code, headers, cookies and decodes body if it 
  1174.     * was encoded in some way
  1175.     *
  1176.     * @access public
  1177.     * @param  bool      Whether to store response body in object property, set
  1178.     *                   this to false if downloading a LARGE file and using a Listener.
  1179.     *                   This is assumed to be true if body is gzip-encoded.
  1180.     * @param  bool      Whether the response can actually have a message-body.
  1181.     *                   Will be set to false for HEAD requests.
  1182.     * @throws PEAR_Error
  1183.     * @return mixed     true on success, PEAR_Error in case of malformed response
  1184.     */
  1185.     function process($saveBody = true, $canHaveBody = true)
  1186.     {
  1187.         do {
  1188.             $line = $this->_sock->readLine();
  1189.             if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) {
  1190.                 return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
  1191.             } else {
  1192.                 $this->_protocol = 'HTTP/' . $http_version;
  1193.                 $this->_code     = intval($returncode);
  1194.             }
  1195.             while ('' !== ($header = $this->_sock->readLine())) {
  1196.                 $this->_processHeader($header);
  1197.             }
  1198.         } while (100 == $this->_code);
  1199.  
  1200.         $this->_notify('gotHeaders', $this->_headers);
  1201.  
  1202.         // RFC 2616, section 4.4:
  1203.         // 1. Any response message which "MUST NOT" include a message-body ... 
  1204.         // is always terminated by the first empty line after the header fields 
  1205.         // 3. ... If a message is received with both a
  1206.         // Transfer-Encoding header field and a Content-Length header field,
  1207.         // the latter MUST be ignored.
  1208.         $canHaveBody = $canHaveBody && $this->_code >= 200 && 
  1209.                        $this->_code != 204 && $this->_code != 304;
  1210.  
  1211.         // If response body is present, read it and decode
  1212.         $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
  1213.         $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
  1214.         $hasBody = false;
  1215.         if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) || 
  1216.                 0 != $this->_headers['content-length']))
  1217.         {
  1218.             if ($chunked || !isset($this->_headers['content-length'])) {
  1219.                 $this->_toRead = null;
  1220.             } else {
  1221.                 $this->_toRead = $this->_headers['content-length'];
  1222.             }
  1223.             while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
  1224.                 if ($chunked) {
  1225.                     $data = $this->_readChunked();
  1226.                 } elseif (is_null($this->_toRead)) {
  1227.                     $data = $this->_sock->read(4096);
  1228.                 } else {
  1229.                     $data = $this->_sock->read(min(4096, $this->_toRead));
  1230.                     $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
  1231.                 }
  1232.                 if ('' == $data) {
  1233.                     break;
  1234.                 } else {
  1235.                     $hasBody = true;
  1236.                     if ($saveBody || $gzipped) {
  1237.                         $this->_body .= $data;
  1238.                     }
  1239.                     $this->_notify($gzipped? 'gzTick': 'tick', $data);
  1240.                 }
  1241.             }
  1242.         }
  1243.  
  1244.         if ($hasBody) {
  1245.             // Uncompress the body if needed
  1246.             if ($gzipped) {
  1247.                 $body = $this->_decodeGzip($this->_body);
  1248.                 if (PEAR::isError($body)) {
  1249.                     return $body;
  1250.                 }
  1251.                 $this->_body = $body;
  1252.                 $this->_notify('gotBody', $this->_body);
  1253.             } else {
  1254.                 $this->_notify('gotBody');
  1255.             }
  1256.         }
  1257.         return true;
  1258.     }
  1259.  
  1260.  
  1261.    /**
  1262.     * Processes the response header
  1263.     *
  1264.     * @access private
  1265.     * @param  string    HTTP header
  1266.     */
  1267.     function _processHeader($header)
  1268.     {
  1269.         if (false === strpos($header, ':')) {
  1270.             return;
  1271.         }
  1272.         list($headername, $headervalue) = explode(':', $header, 2);
  1273.         $headername  = strtolower($headername);
  1274.         $headervalue = ltrim($headervalue);
  1275.         
  1276.         if ('set-cookie' != $headername) {
  1277.             if (isset($this->_headers[$headername])) {
  1278.                 $this->_headers[$headername] .= ',' . $headervalue;
  1279.             } else {
  1280.                 $this->_headers[$headername]  = $headervalue;
  1281.             }
  1282.         } else {
  1283.             $this->_parseCookie($headervalue);
  1284.         }
  1285.     }
  1286.  
  1287.  
  1288.    /**
  1289.     * Parse a Set-Cookie header to fill $_cookies array
  1290.     *
  1291.     * @access private
  1292.     * @param  string    value of Set-Cookie header
  1293.     */
  1294.     function _parseCookie($headervalue)
  1295.     {
  1296.         $cookie = array(
  1297.             'expires' => null,
  1298.             'domain'  => null,
  1299.             'path'    => null,
  1300.             'secure'  => false
  1301.         );
  1302.  
  1303.         // Only a name=value pair
  1304.         if (!strpos($headervalue, ';')) {
  1305.             $pos = strpos($headervalue, '=');
  1306.             $cookie['name']  = trim(substr($headervalue, 0, $pos));
  1307.             $cookie['value'] = trim(substr($headervalue, $pos + 1));
  1308.  
  1309.         // Some optional parameters are supplied
  1310.         } else {
  1311.             $elements = explode(';', $headervalue);
  1312.             $pos = strpos($elements[0], '=');
  1313.             $cookie['name']  = trim(substr($elements[0], 0, $pos));
  1314.             $cookie['value'] = trim(substr($elements[0], $pos + 1));
  1315.  
  1316.             for ($i = 1; $i < count($elements); $i++) {
  1317.                 if (false === strpos($elements[$i], '=')) {
  1318.                     $elName  = trim($elements[$i]);
  1319.                     $elValue = null;
  1320.                 } else {
  1321.                     list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
  1322.                 }
  1323.                 $elName = strtolower($elName);
  1324.                 if ('secure' == $elName) {
  1325.                     $cookie['secure'] = true;
  1326.                 } elseif ('expires' == $elName) {
  1327.                     $cookie['expires'] = str_replace('"', '', $elValue);
  1328.                 } elseif ('path' == $elName || 'domain' == $elName) {
  1329.                     $cookie[$elName] = urldecode($elValue);
  1330.                 } else {
  1331.                     $cookie[$elName] = $elValue;
  1332.                 }
  1333.             }
  1334.         }
  1335.         $this->_cookies[] = $cookie;
  1336.     }
  1337.  
  1338.  
  1339.    /**
  1340.     * Read a part of response body encoded with chunked Transfer-Encoding
  1341.     * 
  1342.     * @access private
  1343.     * @return string
  1344.     */
  1345.     function _readChunked()
  1346.     {
  1347.         // at start of the next chunk?
  1348.         if (0 == $this->_chunkLength) {
  1349.             $line = $this->_sock->readLine();
  1350.             if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
  1351.                 $this->_chunkLength = hexdec($matches[1]); 
  1352.                 // Chunk with zero length indicates the end
  1353.                 if (0 == $this->_chunkLength) {
  1354.                     $this->_sock->readLine(); // make this an eof()
  1355.                     return '';
  1356.                 }
  1357.             } else {
  1358.                 return '';
  1359.             }
  1360.         }
  1361.         $data = $this->_sock->read($this->_chunkLength);
  1362.         $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
  1363.         if (0 == $this->_chunkLength) {
  1364.             $this->_sock->readLine(); // Trailing CRLF
  1365.         }
  1366.         return $data;
  1367.     }
  1368.  
  1369.  
  1370.    /**
  1371.     * Notifies all registered listeners of an event.
  1372.     * 
  1373.     * @param    string  Event name
  1374.     * @param    mixed   Additional data
  1375.     * @access   private
  1376.     * @see HTTP_Request::_notify()
  1377.     */
  1378.     function _notify($event, $data = null)
  1379.     {
  1380.         foreach (array_keys($this->_listeners) as $id) {
  1381.             $this->_listeners[$id]->update($this, $event, $data);
  1382.         }
  1383.     }
  1384.  
  1385.  
  1386.    /**
  1387.     * Decodes the message-body encoded by gzip
  1388.     *
  1389.     * The real decoding work is done by gzinflate() built-in function, this
  1390.     * method only parses the header and checks data for compliance with
  1391.     * RFC 1952  
  1392.     *
  1393.     * @access   private
  1394.     * @param    string  gzip-encoded data
  1395.     * @return   string  decoded data
  1396.     */
  1397.     function _decodeGzip($data)
  1398.     {
  1399.         if (HTTP_REQUEST_MBSTRING) {
  1400.             $oldEncoding = mb_internal_encoding();
  1401.             mb_internal_encoding('iso-8859-1');
  1402.         }
  1403.         $length = strlen($data);
  1404.         // If it doesn't look like gzip-encoded data, don't bother
  1405.         if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
  1406.             return $data;
  1407.         }
  1408.         $method = ord(substr($data, 2, 1));
  1409.         if (8 != $method) {
  1410.             return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
  1411.         }
  1412.         $flags = ord(substr($data, 3, 1));
  1413.         if ($flags & 224) {
  1414.             return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
  1415.         }
  1416.  
  1417.         // header is 10 bytes minimum. may be longer, though.
  1418.         $headerLength = 10;
  1419.         // extra fields, need to skip 'em
  1420.         if ($flags & 4) {
  1421.             if ($length - $headerLength - 2 < 8) {
  1422.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1423.             }
  1424.             $extraLength = unpack('v', substr($data, 10, 2));
  1425.             if ($length - $headerLength - 2 - $extraLength[1] < 8) {
  1426.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1427.             }
  1428.             $headerLength += $extraLength[1] + 2;
  1429.         }
  1430.         // file name, need to skip that
  1431.         if ($flags & 8) {
  1432.             if ($length - $headerLength - 1 < 8) {
  1433.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1434.             }
  1435.             $filenameLength = strpos(substr($data, $headerLength), chr(0));
  1436.             if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
  1437.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1438.             }
  1439.             $headerLength += $filenameLength + 1;
  1440.         }
  1441.         // comment, need to skip that also
  1442.         if ($flags & 16) {
  1443.             if ($length - $headerLength - 1 < 8) {
  1444.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1445.             }
  1446.             $commentLength = strpos(substr($data, $headerLength), chr(0));
  1447.             if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
  1448.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1449.             }
  1450.             $headerLength += $commentLength + 1;
  1451.         }
  1452.         // have a CRC for header. let's check
  1453.         if ($flags & 1) {
  1454.             if ($length - $headerLength - 2 < 8) {
  1455.                 return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
  1456.             }
  1457.             $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
  1458.             $crcStored = unpack('v', substr($data, $headerLength, 2));
  1459.             if ($crcReal != $crcStored[1]) {
  1460.                 return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
  1461.             }
  1462.             $headerLength += 2;
  1463.         }
  1464.         // unpacked data CRC and size at the end of encoded data
  1465.         $tmp = unpack('V2', substr($data, -8));
  1466.         $dataCrc  = $tmp[1];
  1467.         $dataSize = $tmp[2];
  1468.  
  1469.         // finally, call the gzinflate() function
  1470.         $unpacked = @gzinflate(substr($data, $headerLength, -8), $dataSize);
  1471.         if (false === $unpacked) {
  1472.             return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);
  1473.         } elseif ($dataSize != strlen($unpacked)) {
  1474.             return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);
  1475.         } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
  1476.             return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
  1477.         }
  1478.         if (HTTP_REQUEST_MBSTRING) {
  1479.             mb_internal_encoding($oldEncoding);
  1480.         }
  1481.         return $unpacked;
  1482.     }
  1483. } // End class HTTP_Response
  1484. ?>
  1485.