[ Index ]
 

Code source de Serendipity 1.2

Accédez au Source d'autres logiciels libres

title

Body

[fermer]

/bundled-libs/HTTP/ -> Request.php (source)

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


Généré le : Sat Nov 24 09:00:37 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics