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

Documentation generated on Mon, 11 Mar 2019 13:56:25 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.