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

Source for file Client.php

Documentation is available at Client.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at                              |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Author: Alexey Borzov <avb@php.net>                                  |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Client.php,v 1.7 2006/06/03 08:54:10 avb Exp $
  20.  
  21. /*
  22.  * Do this define in your script if you wish HTTP_Client to follow browser 
  23.  * quirks rather than HTTP specification (RFC2616). This means:
  24.  *   - do a GET request after redirect with code 301, rather than use the
  25.  *     same method as before redirect.
  26.  */
  27. // define('HTTP_CLIENT_QUIRK_MODE', true);
  28.  
  29. require_once 'HTTP/Request.php';
  30. require_once 'HTTP/Client/CookieManager.php';
  31.  
  32. /**
  33.  * A simple HTTP client class.
  34.  * 
  35.  * The class wraps around HTTP_Request providing a higher-level
  36.  * API for performing multiple HTTP requests
  37.  * 
  38.  * @package HTTP_Client
  39.  * @author Alexey Borzov <avb@php.net>
  40.  * @version $Revision: 1.7 $
  41.  */
  42. {
  43.    /**
  44.     * An HTTP_Client_CookieManager instance
  45.     * @var object 
  46.     */
  47.     var $_cookieManager;
  48.  
  49.    /**
  50.     * Received HTTP responses
  51.     * @var array 
  52.     */
  53.     var $_responses;
  54.  
  55.    /**
  56.     * Default headers to send on every request
  57.     * @var array 
  58.     */
  59.     var $_defaultHeaders = array();
  60.  
  61.    /**
  62.     * Default parameters for HTTP_Request's constructor
  63.     * @var array 
  64.     */
  65.     var $_defaultRequestParams = array();
  66.  
  67.    /**
  68.     * How many redirects were done
  69.     * @var integer 
  70.     */
  71.     var $_redirectCount = 0;
  72.  
  73.    /**
  74.     * Maximum allowed redirects
  75.     * @var integer 
  76.     */
  77.     var $_maxRedirects = 5;
  78.  
  79.    /**
  80.     * Listeners attached to the client
  81.     * @var array 
  82.     */
  83.     var $_listeners = array();
  84.  
  85.    /**
  86.     * Whether the listener should be propagated to Request objects
  87.     * @var array 
  88.     */
  89.     var $_propagate = array();
  90.  
  91.    /**
  92.     * Whether to keep all the responses or just the most recent one
  93.     * @var boolean 
  94.     */
  95.     var $_isHistoryEnabled = true;
  96.  
  97.    /**
  98.     * Constructor
  99.     * 
  100.     * @access   public
  101.     * @param    array   Parameters to pass to HTTP_Request's constructor
  102.     * @param    array   Default headers to send on every request
  103.     * @param    object  HTTP_Client_CookieManager   Cookie manager object to use
  104.     */
  105.     function HTTP_Client($defaultRequestParams = null$defaultHeaders = null$cookieManager = null)
  106.     {
  107.         if (!empty($cookieManager&& is_a($cookieManager'HTTP_Client_CookieManager')) {
  108.             $this->_cookieManager $cookieManager;
  109.         else {
  110.             $this->_cookieManager =new HTTP_Client_CookieManager();
  111.         }
  112.         if (isset($defaultHeaders)) {
  113.             $this->setDefaultHeader($defaultHeaders);
  114.         }
  115.         if (isset($defaultRequestParams)) {
  116.             $this->setRequestParameter($defaultRequestParams);
  117.         }
  118.     }
  119.  
  120.  
  121.    /**
  122.     * Sets the maximum redirects that will be processed.
  123.     * 
  124.     * Setting this to 0 disables redirect processing. If not 0 and the
  125.     * number of redirects in a request is bigger than this number, then an
  126.     * error will be raised.
  127.     * 
  128.     * @access   public
  129.     * @param    int     Max number of redirects to process
  130.     */
  131.     function setMaxRedirects($value)
  132.     {
  133.         $this->_maxRedirects $value;
  134.     }
  135.  
  136.  
  137.    /**
  138.     * Sets whether to keep all the responses or just the most recent one
  139.     *
  140.     * @access public
  141.     * @param  bool      Whether to enable history
  142.     */
  143.     function enableHistory($enable)
  144.     {
  145.         $this->_isHistoryEnabled = (bool)$enable;
  146.     }
  147.  
  148.    /**
  149.     * Creates a HTTP_Request objects, applying all the necessary defaults
  150.     *
  151.     * @param    string   URL
  152.     * @param    string   Method, constants are defined in HTTP_Request
  153.     * @param    array    Extra headers to send
  154.     * @access   private
  155.     * @return   object   HTTP_Request object with all defaults applied
  156.     */
  157.     function &_createRequest($url$method = HTTP_REQUEST_METHOD_GET$headers = array())
  158.     {
  159.         $req =new HTTP_Request($url$this->_defaultRequestParams);
  160.         $req->setMethod($method);
  161.         foreach ($this->_defaultHeaders as $name => $value{
  162.             $req->addHeader($name$value);
  163.         }
  164.         foreach ($headers as $name => $value{
  165.             $req->addHeader($name$value);
  166.         }
  167.         $this->_cookieManager->passCookies($req);
  168.         foreach ($this->_propagate as $id => $propagate{
  169.             if ($propagate{
  170.                 $req->attach($this->_listeners[$id]);
  171.             }
  172.         }
  173.         return $req;
  174.     }
  175.     
  176.  
  177.    /**
  178.     * Sends a 'HEAD' HTTP request
  179.     *
  180.     * @param    string  URL
  181.     * @param    array   Extra headers to send
  182.     * @access   public
  183.     * @return   integer HTTP response code
  184.     * @throws   PEAR_Error
  185.     */
  186.     function head($url$headers = array())
  187.     {
  188.         $request =$this->_createRequest($urlHTTP_REQUEST_METHOD_HEAD$headers);
  189.         return $this->_performRequest($request);
  190.     }
  191.    
  192.  
  193.    /**
  194.     * Sends a 'GET' HTTP request
  195.     * 
  196.     * @param    string  URL
  197.     * @param    mixed   additional data to send
  198.     * @param    boolean Whether the data is already urlencoded
  199.     * @param    array   Extra headers to send
  200.     * @access   public
  201.     * @return   integer HTTP response code
  202.     * @throws   PEAR_Error
  203.     */
  204.     function get($url$data = null$preEncoded = false$headers = array())
  205.     {
  206.         $request =$this->_createRequest($urlHTTP_REQUEST_METHOD_GET$headers);
  207.         if (is_array($data)) {
  208.             foreach ($data as $name => $value{
  209.                 $request->addQueryString($name$value$preEncoded);
  210.             }
  211.         elseif (isset($data)) {
  212.             $request->addRawQueryString($data$preEncoded);
  213.         }
  214.         return $this->_performRequest($request);
  215.     }
  216.  
  217.  
  218.    /**
  219.     * Sends a 'POST' HTTP request
  220.     *
  221.     * @param    string  URL
  222.     * @param    mixed   Data to send
  223.     * @param    boolean Whether the data is already urlencoded
  224.     * @param    array   Files to upload. Elements of the array should have the form:
  225.     *                    array(name, filename(s)[, content type]), see HTTP_Request::addFile()
  226.     * @param    array   Extra headers to send
  227.     * @access   public
  228.     * @return   integer HTTP response code
  229.     * @throws   PEAR_Error
  230.     */
  231.     function post($url$data$preEncoded = false$files = array()$headers = array())
  232.     {
  233.         $request =$this->_createRequest($urlHTTP_REQUEST_METHOD_POST$headers);
  234.         if (is_array($data)) {
  235.             foreach ($data as $name => $value{
  236.                 $request->addPostData($name$value$preEncoded);
  237.             }
  238.         else {
  239.             $request->addRawPostData($data$preEncoded);
  240.         }
  241.         foreach ($files as $fileData{
  242.             $res call_user_func_array(array(&$request'addFile')$fileData);
  243.             if (PEAR::isError($res)) {
  244.                 return $res;
  245.             }
  246.         }
  247.         return $this->_performRequest($request);
  248.     }
  249.  
  250.  
  251.    /**
  252.     * Sets default header(s) for HTTP requests
  253.     *
  254.     * @param    mixed   header name or array ('header name' => 'header value')
  255.     * @param    string  header value if $name is not an array
  256.     * @access   public
  257.     */
  258.     function setDefaultHeader($name$value = null)
  259.     {
  260.         if (is_array($name)) {
  261.             $this->_defaultHeaders array_merge($this->_defaultHeaders$name);
  262.         else {
  263.             $this->_defaultHeaders[$name$value;
  264.         }
  265.     }
  266.  
  267.  
  268.    /**
  269.     * Sets parameter(s) for HTTP requests
  270.     *
  271.     * @param    mixed   parameter name or array ('parameter name' => 'parameter value')
  272.     * @param    string  parameter value if $name is not an array
  273.     * @access   public
  274.     */
  275.     function setRequestParameter($name$value = null)
  276.     {
  277.         if (is_array($name)) {
  278.             $this->_defaultRequestParams array_merge($this->_defaultRequestParams$name);
  279.         else {
  280.             $this->_defaultRequestParams[$name$value;
  281.         }
  282.     }
  283.       
  284.  
  285.    /**
  286.     * Performs a request, processes redirects
  287.     *
  288.     * @param    object  HTTP_Request object
  289.     * @access   private
  290.     * @return   integer HTTP response code
  291.     * @throws   PEAR_Error
  292.     */
  293.     function _performRequest(&$request)
  294.     {
  295.         // If this is not a redirect, notify the listeners of new request
  296.         if (0 == $this->_redirectCount && !empty($request->_url)) {
  297.             $this->_notify('request'$request->_url->getUrl());
  298.         }
  299.         if (PEAR::isError($err $request->sendRequest())) {
  300.             $this->_redirectCount = 0;
  301.             return $err;
  302.         }
  303.         $this->_pushResponse($request);
  304.  
  305.         $code $request->getResponseCode();
  306.         if ($this->_maxRedirects > 0{
  307.             if (in_array($codearray(300301302303307))) {
  308.                 if ('' == ($location $request->getResponseHeader('Location'))) {
  309.                     $this->_redirectCount = 0;
  310.                     return PEAR::raiseError("No 'Location' field on redirect");
  311.                 }
  312.                 // Bug #5759: do not try to follow non-HTTP redirects
  313.                 if (null === ($redirectUrl $this->_redirectUrl($request->_url$location))) {
  314.                     $this->_redirectCount = 0;
  315.                     return $code;
  316.                 }
  317.             // Redirect via <meta http-equiv="Refresh"> tag, see request #5734
  318.             elseif (200 <= $code && $code < 300{
  319.                 $redirectUrl $this->_getMetaRedirect($request);
  320.             }
  321.         }
  322.         if (!empty($redirectUrl)) {
  323.             if (++$this->_redirectCount $this->_maxRedirects{
  324.                 $this->_redirectCount = 0;
  325.                 return PEAR::raiseError('Too many redirects');
  326.             }
  327.             // Notify of redirection
  328.             $this->_notify('httpRedirect'$redirectUrl);
  329.             // we access the private properties directly, as there are no accessors for them
  330.             switch ($request->_method{
  331.                 case HTTP_REQUEST_METHOD_POST: 
  332.                     if (302 == $code || 303 == $code || (301 == $code && defined('HTTP_CLIENT_QUIRK_MODE'))) {
  333.                         return $this->get($redirectUrl);
  334.                     else {
  335.                         $postFiles = array();
  336.                         foreach ($request->_postFiles as $name => $data{
  337.                             $postFiles[= array($name$data['name']$data['type']);
  338.                         }
  339.                         return $this->post($redirectUrl$request->_postDatatrue$postFiles);
  340.                     }
  341.                 case HTTP_REQUEST_METHOD_HEAD:
  342.                     return (303 == $code$this->get($redirectUrl)$this->head($redirectUrl));
  343.                 case HTTP_REQUEST_METHOD_GET: 
  344.                 default:
  345.                     return $this->get($redirectUrl);
  346.             // switch
  347.  
  348.         else {
  349.             $this->_redirectCount = 0;
  350.             if (400 >= $code{
  351.                 $this->_notify('httpSuccess');
  352.                 $this->setDefaultHeader('Referer'$request->_url->getUrl());
  353.                 // some result processing should go here
  354.             else {
  355.                 $this->_notify('httpError');
  356.             }
  357.         }
  358.         return $code;
  359.     }
  360.  
  361.  
  362.    /**
  363.     * Returns the most recent HTTP response
  364.     * 
  365.     * @access public
  366.     * @return array 
  367.     */
  368.     function &currentResponse()
  369.     {
  370.         return $this->_responses[count($this->_responses- 1];
  371.     }
  372.  
  373.  
  374.    /**
  375.     * Saves the server's response to responses list
  376.     *
  377.     * @param    object  HTTP_Request object, with request already sent
  378.     * @access   private
  379.     */
  380.     function _pushResponse(&$request)
  381.     {
  382.         $this->_cookieManager->updateCookies($request);
  383.         $idx   $this->_isHistoryEnabledcount($this->_responses): 0;
  384.         $this->_responses[$idx= array(
  385.             'code'    => $request->getResponseCode(),
  386.             'headers' => $request->getResponseHeader(),
  387.             'body'    => $request->getResponseBody()
  388.         );
  389.     }
  390.  
  391.  
  392.    /**
  393.     * Clears object's internal properties
  394.     *
  395.     * @access public
  396.     */
  397.     function reset()
  398.     {
  399.         $this->_cookieManager->reset();
  400.         $this->_responses            = array();
  401.         $this->_defaultHeaders       = array();
  402.         $this->_defaultRequestParams = array();
  403.     }
  404.  
  405.  
  406.    /**
  407.     * Adds a Listener to the list of listeners that are notified of
  408.     * the object's events
  409.     * 
  410.     * @param    object   HTTP_Request_Listener instance to attach
  411.     * @param    boolean  Whether the listener should be attached to the
  412.     *                     created HTTP_Request objects
  413.     * @return   boolean  whether the listener was successfully attached
  414.     * @access   public
  415.     */
  416.     function attach(&$listener$propagate = false)
  417.     {
  418.         if (!is_a($listener'HTTP_Request_Listener')) {
  419.             return false;
  420.         }
  421.         $this->_listeners[$listener->getId()=$listener;
  422.         $this->_propagate[$listener->getId()=  $propagate;
  423.         return true;
  424.     }
  425.  
  426.  
  427.    /**
  428.     * Removes a Listener from the list of listeners
  429.     * 
  430.     * @param    object   HTTP_Request_Listener instance to detach
  431.     * @return   boolean  whether the listener was successfully detached
  432.     * @access   public
  433.     */
  434.     function detach(&$listener)
  435.     {
  436.         if (!is_a($listener'HTTP_Request_Listener'|| 
  437.             !isset($this->_listeners[$listener->getId()])) {
  438.             return false;
  439.         }
  440.         unset($this->_listeners[$listener->getId()]$this->_propagate[$listener->getId()]);
  441.         return true;
  442.     }
  443.  
  444.  
  445.    /**
  446.     * Notifies all registered listeners of an event.
  447.     * 
  448.     * Currently available events are:
  449.     * 'request': sent on HTTP request that is not a redirect
  450.     * 'httpSuccess': sent when we receive a successfull 2xx response
  451.     * 'httpRedirect': sent when we receive a redirection response
  452.     * 'httpError': sent on 4xx, 5xx response
  453.     * 
  454.     * @param    string  Event name
  455.     * @param    mixed   Additional data
  456.     * @access   private
  457.     */
  458.     function _notify($event$data = null)
  459.     {
  460.         foreach (array_keys($this->_listenersas $id{
  461.             $this->_listeners[$id]->update($this$event$data);
  462.         }
  463.     }
  464.  
  465.  
  466.    /**
  467.     * Calculates the absolute URL of a redirect
  468.     *  
  469.     * @param    object  Net_Url object containing the request URL
  470.     * @param    string  Value of the 'Location' response header
  471.     * @return   string|null Absolute URL we are being redirected to, null in case of non-HTTP URL
  472.     * @access   private
  473.     */
  474.     function _redirectUrl($url$location)
  475.     {
  476.         // If it begins with a scheme (as defined in RFC 2396) then it is absolute URI 
  477.         if (preg_match('/^([a-zA-Z][a-zA-Z0-9+.-]*):/'$location$matches)) {
  478.             // Bug #5759: we shouldn't try to follow non-HTTP redirects
  479.             if ('http' == strtolower($matches[1]|| 'https' == strtolower($matches[1])) {
  480.                 return $location;
  481.             else {
  482.                 return null;
  483.             }
  484.         else {
  485.             if ('/' == $location{0}{
  486.                 $url->path = Net_URL::resolvePath($location);
  487.             elseif('/' == substr($url->path-1)) {
  488.                 $url->path = Net_URL::resolvePath($url->path . $location);
  489.             else {
  490.                 $dirname (DIRECTORY_SEPARATOR == dirname($url->path)'/'dirname($url->path));
  491.                 $url->path = Net_URL::resolvePath($dirname '/' $location);
  492.             }
  493.             $url->querystring = array();
  494.             $url->anchor      = '';
  495.             return $url->getUrl();
  496.         }
  497.     }
  498.  
  499.  
  500.    /**
  501.     * Returns the cookie manager object (e.g. for storing it somewhere)
  502.     *
  503.     * @return object HTTP_Client_CookieManager 
  504.     * @access public
  505.     */
  506.     function getCookieManager()
  507.     {
  508.         return $this->_cookieManager;
  509.     }
  510.  
  511.  
  512.    /**
  513.     * Tries to extract a redirect URL from <meta http-equiv=Refresh> tag (request #5734)
  514.     *
  515.     * @param    object HTTP_Request     A request object containing the response
  516.     * @return   string|null            Absolute URI we are being redirected to, null if no redirect / invalid redirect
  517.     * @access   private
  518.     */
  519.     function _getMetaRedirect(&$request)
  520.     {
  521.         // Non-HTML response or empty response body
  522.         if ('text/html' != substr($request->getResponseHeader('content-type')09||
  523.             '' == ($body $request->getResponseBody())) {
  524.             return null;
  525.         }
  526.         // No <meta http-equiv=Refresh> tag
  527.         if (!preg_match('!<meta\\s+([^>]*http-equiv\\s*=\\s*("Refresh"|\'Refresh\'|Refresh)[^>]*)>!is'$body$matches)) {
  528.             return null;
  529.         }
  530.         // Just a refresh, no redirect
  531.         if (!preg_match('!content\\s*=\\s*("[^"]+"|\'[^\']+\'|\\S+)!is'$matches[1]$urlMatches)) {
  532.             return null;
  533.         }
  534.         $parts explode(';'('\'' == substr($urlMatches[1]01|| '"' == substr($urlMatches[1]01))
  535.                                substr($urlMatches[1]1-1)$urlMatches[1]);
  536.         if (empty($parts[1]|| !preg_match('/url\\s*=\\s*(\\S+)/is'$parts[1]$urlMatches)) {
  537.              return null;
  538.         }
  539.         // We do finally have an url... Now check that it's:
  540.         // a) HTTP, b) not to the same page
  541.         $previousUrl $request->_url->getUrl();
  542.         $redirectUrl $this->_redirectUrl($request->_urlhtml_entity_decode($urlMatches[1]));
  543.         return (null === $redirectUrl || $redirectUrl == $previousUrl)? null: $redirectUrl
  544.     }
  545. }
  546. ?>

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