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

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