HTTP_Request
[ class tree: HTTP_Request ] [ index: HTTP_Request ] [ all elements ]

Source for file Request.php

Documentation is available at Request.php

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

Documentation generated on Mon, 11 Mar 2019 14:10:29 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.