[ PHPXref.com ] [ Generated: Sun Jul 20 19:38:39 2008 ] [ phpFlickr 2.0.0 ]
[ Index ]     [ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

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


[ Powered by PHPXref - Served by Debian GNU/Linux ]