[ Index ]

PHP Cross Reference of vtigercrm-6.1.0

title

Body

[close]

/vtlib/thirdparty/network/ -> Request.php (source)

   1  <?php
   2  /**
   3   * Class for performing HTTP requests
   4   *
   5   * PHP versions 4 and 5
   6   *
   7   * LICENSE:
   8   *
   9   * Copyright (c) 2002-2007, Richard Heyes
  10   * All rights reserved.
  11   *
  12   * Redistribution and use in source and binary forms, with or without
  13   * modification, are permitted provided that the following conditions
  14   * are met:
  15   *
  16   * o Redistributions of source code must retain the above copyright
  17   *   notice, this list of conditions and the following disclaimer.
  18   * o Redistributions in binary form must reproduce the above copyright
  19   *   notice, this list of conditions and the following disclaimer in the
  20   *   documentation and/or other materials provided with the distribution.
  21   * o The names of the authors may not be used to endorse or promote
  22   *   products derived from this software without specific prior written
  23   *   permission.
  24   *
  25   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  26   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  27   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  28   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  29   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  30   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  31   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  32   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  33   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  34   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  35   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36   *
  37   * @category    HTTP
  38   * @package     HTTP_Request
  39   * @author      Richard Heyes <[email protected]>
  40   * @author      Alexey Borzov <[email protected]>
  41   * @copyright   2002-2007 Richard Heyes
  42   * @license     http://opensource.org/licenses/bsd-license.php New BSD License
  43   * @version     CVS: $Id: Request.php,v 1.63 2008/10/11 11:07:10 avb Exp $
  44   * @link        http://pear.php.net/package/HTTP_Request/
  45   */
  46  
  47  /**
  48   * PEAR and PEAR_Error classes (for error handling)
  49   */
  50  require_once dirname(__FILE__) . '/PEAR.php';
  51  
  52  /**
  53   * Socket class
  54   */
  55  require_once dirname(__FILE__) . '/Net/Socket.php';
  56  /**
  57   * URL handling class
  58   */
  59  require_once dirname(__FILE__) . '/Net/URL.php';
  60  
  61  /**#@+
  62   * Constants for HTTP request methods
  63   */
  64  define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
  65  define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
  66  define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
  67  define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
  68  define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
  69  define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
  70  define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
  71  /**#@-*/
  72  
  73  /**#@+
  74   * Constants for HTTP request error codes
  75   */
  76  define('HTTP_REQUEST_ERROR_FILE',             1);
  77  define('HTTP_REQUEST_ERROR_URL',              2);
  78  define('HTTP_REQUEST_ERROR_PROXY',            4);
  79  define('HTTP_REQUEST_ERROR_REDIRECTS',        8);
  80  define('HTTP_REQUEST_ERROR_RESPONSE',        16);
  81  define('HTTP_REQUEST_ERROR_GZIP_METHOD',     32);
  82  define('HTTP_REQUEST_ERROR_GZIP_READ',       64);
  83  define('HTTP_REQUEST_ERROR_GZIP_DATA',      128);
  84  define('HTTP_REQUEST_ERROR_GZIP_CRC',       256);
  85  /**#@-*/
  86  
  87  /**#@+
  88   * Constants for HTTP protocol versions
  89   */
  90  define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
  91  define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
  92  /**#@-*/
  93  
  94  if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
  95     /**
  96      * Whether string functions are overloaded by their mbstring equivalents
  97      */
  98      define('HTTP_REQUEST_MBSTRING', true);
  99  } else {
 100     /**
 101      * @ignore
 102      */
 103      define('HTTP_REQUEST_MBSTRING', false);
 104  }
 105  
 106  /**
 107   * Class for performing HTTP requests
 108   *
 109   * Simple example (fetches yahoo.com and displays it):
 110   * <code>
 111   * $a = &new HTTP_Request('http://www.yahoo.com/');
 112   * $a->sendRequest();
 113   * echo $a->getResponseBody();
 114   * </code>
 115   *
 116   * @category    HTTP
 117   * @package     HTTP_Request
 118   * @author      Richard Heyes <[email protected]>
 119   * @author      Alexey Borzov <[email protected]>
 120   * @version     Release: 1.4.4
 121   */
 122  class HTTP_Request
 123  {
 124     /**#@+
 125      * @access private
 126      */
 127      /**
 128      * Instance of Net_URL
 129      * @var Net_URL
 130      */
 131      var $_url;
 132  
 133      /**
 134      * Type of request
 135      * @var string
 136      */
 137      var $_method;
 138  
 139      /**
 140      * HTTP Version
 141      * @var string
 142      */
 143      var $_http;
 144  
 145      /**
 146      * Request headers
 147      * @var array
 148      */
 149      var $_requestHeaders;
 150  
 151      /**
 152      * Basic Auth Username
 153      * @var string
 154      */
 155      var $_user;
 156  
 157      /**
 158      * Basic Auth Password
 159      * @var string
 160      */
 161      var $_pass;
 162  
 163      /**
 164      * Socket object
 165      * @var Net_Socket
 166      */
 167      var $_sock;
 168  
 169      /**
 170      * Proxy server
 171      * @var string
 172      */
 173      var $_proxy_host;
 174  
 175      /**
 176      * Proxy port
 177      * @var integer
 178      */
 179      var $_proxy_port;
 180  
 181      /**
 182      * Proxy username
 183      * @var string
 184      */
 185      var $_proxy_user;
 186  
 187      /**
 188      * Proxy password
 189      * @var string
 190      */
 191      var $_proxy_pass;
 192  
 193      /**
 194      * Post data
 195      * @var array
 196      */
 197      var $_postData;
 198  
 199     /**
 200      * Request body
 201      * @var string
 202      */
 203      var $_body;
 204  
 205     /**
 206      * A list of methods that MUST NOT have a request body, per RFC 2616
 207      * @var array
 208      */
 209      var $_bodyDisallowed = array('TRACE');
 210  
 211     /**
 212      * Methods having defined semantics for request body
 213      *
 214      * Content-Length header (indicating that the body follows, section 4.3 of
 215      * RFC 2616) will be sent for these methods even if no body was added
 216      *
 217      * @var array
 218      */
 219      var $_bodyRequired = array('POST', 'PUT');
 220  
 221     /**
 222      * Files to post
 223      * @var array
 224      */
 225      var $_postFiles = array();
 226  
 227      /**
 228      * Connection timeout.
 229      * @var float
 230      */
 231      var $_timeout;
 232  
 233      /**
 234      * HTTP_Response object
 235      * @var HTTP_Response
 236      */
 237      var $_response;
 238  
 239      /**
 240      * Whether to allow redirects
 241      * @var boolean
 242      */
 243      var $_allowRedirects;
 244  
 245      /**
 246      * Maximum redirects allowed
 247      * @var integer
 248      */
 249      var $_maxRedirects;
 250  
 251      /**
 252      * Current number of redirects
 253      * @var integer
 254      */
 255      var $_redirects;
 256  
 257     /**
 258      * Whether to append brackets [] to array variables
 259      * @var bool
 260      */
 261      var $_useBrackets = true;
 262  
 263     /**
 264      * Attached listeners
 265      * @var array
 266      */
 267      var $_listeners = array();
 268  
 269     /**
 270      * Whether to save response body in response object property
 271      * @var bool
 272      */
 273      var $_saveBody = true;
 274  
 275     /**
 276      * Timeout for reading from socket (array(seconds, microseconds))
 277      * @var array
 278      */
 279      var $_readTimeout = null;
 280  
 281     /**
 282      * Options to pass to Net_Socket::connect. See stream_context_create
 283      * @var array
 284      */
 285      var $_socketOptions = null;
 286     /**#@-*/
 287  
 288      /**
 289      * Constructor
 290      *
 291      * Sets up the object
 292      * @param    string  The url to fetch/access
 293      * @param    array   Associative array of parameters which can have the following keys:
 294      * <ul>
 295      *   <li>method         - Method to use, GET, POST etc (string)</li>
 296      *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>
 297      *   <li>user           - Basic Auth username (string)</li>
 298      *   <li>pass           - Basic Auth password (string)</li>
 299      *   <li>proxy_host     - Proxy server host (string)</li>
 300      *   <li>proxy_port     - Proxy server port (integer)</li>
 301      *   <li>proxy_user     - Proxy auth username (string)</li>
 302      *   <li>proxy_pass     - Proxy auth password (string)</li>
 303      *   <li>timeout        - Connection timeout in seconds (float)</li>
 304      *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>
 305      *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>
 306      *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>
 307      *   <li>saveBody       - Whether to save response body in response object property (bool)</li>
 308      *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>
 309      *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>
 310      * </ul>
 311      * @access public
 312      */
 313      function HTTP_Request($url = '', $params = array())
 314      {
 315          $this->_method         =  HTTP_REQUEST_METHOD_GET;
 316          $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
 317          $this->_requestHeaders = array();
 318          $this->_postData       = array();
 319          $this->_body           = null;
 320  
 321          $this->_user = null;
 322          $this->_pass = null;
 323  
 324          $this->_proxy_host = null;
 325          $this->_proxy_port = null;
 326          $this->_proxy_user = null;
 327          $this->_proxy_pass = null;
 328  
 329          $this->_allowRedirects = false;
 330          $this->_maxRedirects   = 3;
 331          $this->_redirects      = 0;
 332  
 333          $this->_timeout  = null;
 334          $this->_response = null;
 335  
 336          foreach ($params as $key => $value) {
 337              $this->{'_' . $key} = $value;
 338          }
 339  
 340          if (!empty($url)) {
 341              $this->setURL($url);
 342          }
 343  
 344          // Default useragent
 345          $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
 346  
 347          // We don't do keep-alives by default
 348          $this->addHeader('Connection', 'close');
 349  
 350          // Basic authentication
 351          if (!empty($this->_user)) {
 352              $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
 353          }
 354  
 355          // Proxy authentication (see bug #5913)
 356          if (!empty($this->_proxy_user)) {
 357              $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
 358          }
 359  
 360          // Use gzip encoding if possible
 361          if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {
 362              $this->addHeader('Accept-Encoding', 'gzip');
 363          }
 364      }
 365  
 366      /**
 367      * Generates a Host header for HTTP/1.1 requests
 368      *
 369      * @access private
 370      * @return string
 371      */
 372      function _generateHostHeader()
 373      {
 374          if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
 375              $host = $this->_url->host . ':' . $this->_url->port;
 376  
 377          } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
 378              $host = $this->_url->host . ':' . $this->_url->port;
 379  
 380          } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
 381              $host = $this->_url->host . ':' . $this->_url->port;
 382  
 383          } else {
 384              $host = $this->_url->host;
 385          }
 386  
 387          return $host;
 388      }
 389  
 390      /**
 391      * Resets the object to its initial state (DEPRECATED).
 392      * Takes the same parameters as the constructor.
 393      *
 394      * @param  string $url    The url to be requested
 395      * @param  array  $params Associative array of parameters
 396      *                        (see constructor for details)
 397      * @access public
 398      * @deprecated deprecated since 1.2, call the constructor if this is necessary
 399      */
 400      function reset($url, $params = array())
 401      {
 402          $this->HTTP_Request($url, $params);
 403      }
 404  
 405      /**
 406      * Sets the URL to be requested
 407      *
 408      * @param  string The url to be requested
 409      * @access public
 410      */
 411      function setURL($url)
 412      {
 413          $this->_url = &new Net_URL($url, $this->_useBrackets);
 414  
 415          if (!empty($this->_url->user) || !empty($this->_url->pass)) {
 416              $this->setBasicAuth($this->_url->user, $this->_url->pass);
 417          }
 418  
 419          if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
 420              $this->addHeader('Host', $this->_generateHostHeader());
 421          }
 422  
 423          // set '/' instead of empty path rather than check later (see bug #8662)
 424          if (empty($this->_url->path)) {
 425              $this->_url->path = '/';
 426          }
 427      }
 428  
 429     /**
 430      * Returns the current request URL
 431      *
 432      * @return   string  Current request URL
 433      * @access   public
 434      */
 435      function getUrl()
 436      {
 437          return empty($this->_url)? '': $this->_url->getUrl();
 438      }
 439  
 440      /**
 441      * Sets a proxy to be used
 442      *
 443      * @param string     Proxy host
 444      * @param int        Proxy port
 445      * @param string     Proxy username
 446      * @param string     Proxy password
 447      * @access public
 448      */
 449      function setProxy($host, $port = 8080, $user = null, $pass = null)
 450      {
 451          $this->_proxy_host = $host;
 452          $this->_proxy_port = $port;
 453          $this->_proxy_user = $user;
 454          $this->_proxy_pass = $pass;
 455  
 456          if (!empty($user)) {
 457              $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
 458          }
 459      }
 460  
 461      /**
 462      * Sets basic authentication parameters
 463      *
 464      * @param string     Username
 465      * @param string     Password
 466      */
 467      function setBasicAuth($user, $pass)
 468      {
 469          $this->_user = $user;
 470          $this->_pass = $pass;
 471  
 472          $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
 473      }
 474  
 475      /**
 476      * Sets the method to be used, GET, POST etc.
 477      *
 478      * @param string     Method to use. Use the defined constants for this
 479      * @access public
 480      */
 481      function setMethod($method)
 482      {
 483          $this->_method = $method;
 484      }
 485  
 486      /**
 487      * Sets the HTTP version to use, 1.0 or 1.1
 488      *
 489      * @param string     Version to use. Use the defined constants for this
 490      * @access public
 491      */
 492      function setHttpVer($http)
 493      {
 494          $this->_http = $http;
 495      }
 496  
 497      /**
 498      * Adds a request header
 499      *
 500      * @param string     Header name
 501      * @param string     Header value
 502      * @access public
 503      */
 504      function addHeader($name, $value)
 505      {
 506          $this->_requestHeaders[strtolower($name)] = $value;
 507      }
 508  
 509      /**
 510      * Removes a request header
 511      *
 512      * @param string     Header name to remove
 513      * @access public
 514      */
 515      function removeHeader($name)
 516      {
 517          if (isset($this->_requestHeaders[strtolower($name)])) {
 518              unset($this->_requestHeaders[strtolower($name)]);
 519          }
 520      }
 521  
 522      /**
 523      * Adds a querystring parameter
 524      *
 525      * @param string     Querystring parameter name
 526      * @param string     Querystring parameter value
 527      * @param bool       Whether the value is already urlencoded or not, default = not
 528      * @access public
 529      */
 530      function addQueryString($name, $value, $preencoded = false)
 531      {
 532          $this->_url->addQueryString($name, $value, $preencoded);
 533      }
 534  
 535      /**
 536      * Sets the querystring to literally what you supply
 537      *
 538      * @param string     The querystring data. Should be of the format foo=bar&x=y etc
 539      * @param bool       Whether data is already urlencoded or not, default = already encoded
 540      * @access public
 541      */
 542      function addRawQueryString($querystring, $preencoded = true)
 543      {
 544          $this->_url->addRawQueryString($querystring, $preencoded);
 545      }
 546  
 547      /**
 548      * Adds postdata items
 549      *
 550      * @param string     Post data name
 551      * @param string     Post data value
 552      * @param bool       Whether data is already urlencoded or not, default = not
 553      * @access public
 554      */
 555      function addPostData($name, $value, $preencoded = false)
 556      {
 557          if ($preencoded) {
 558              $this->_postData[$name] = $value;
 559          } else {
 560              $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
 561          }
 562      }
 563  
 564     /**
 565      * Recursively applies the callback function to the value
 566      *
 567      * @param    mixed   Callback function
 568      * @param    mixed   Value to process
 569      * @access   private
 570      * @return   mixed   Processed value
 571      */
 572      function _arrayMapRecursive($callback, $value)
 573      {
 574          if (!is_array($value)) {
 575              return call_user_func($callback, $value);
 576          } else {
 577              $map = array();
 578              foreach ($value as $k => $v) {
 579                  $map[$k] = $this->_arrayMapRecursive($callback, $v);
 580              }
 581              return $map;
 582          }
 583      }
 584  
 585     /**
 586      * Adds a file to form-based file upload
 587      *
 588      * Used to emulate file upload via a HTML form. The method also sets
 589      * Content-Type of HTTP request to 'multipart/form-data'.
 590      *
 591      * If you just want to send the contents of a file as the body of HTTP
 592      * request you should use setBody() method.
 593      *
 594      * @access public
 595      * @param  string    name of file-upload field
 596      * @param  mixed     file name(s)
 597      * @param  mixed     content-type(s) of file(s) being uploaded
 598      * @return bool      true on success
 599      * @throws PEAR_Error
 600      */
 601      function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
 602      {
 603          if (!is_array($fileName) && !is_readable($fileName)) {
 604              return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);
 605          } elseif (is_array($fileName)) {
 606              foreach ($fileName as $name) {
 607                  if (!is_readable($name)) {
 608                      return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);
 609                  }
 610              }
 611          }
 612          $this->addHeader('Content-Type', 'multipart/form-data');
 613          $this->_postFiles[$inputName] = array(
 614              'name' => $fileName,
 615              'type' => $contentType
 616          );
 617          return true;
 618      }
 619  
 620      /**
 621      * Adds raw postdata (DEPRECATED)
 622      *
 623      * @param string     The data
 624      * @param bool       Whether data is preencoded or not, default = already encoded
 625      * @access public
 626      * @deprecated       deprecated since 1.3.0, method setBody() should be used instead
 627      */
 628      function addRawPostData($postdata, $preencoded = true)
 629      {
 630          $this->_body = $preencoded ? $postdata : urlencode($postdata);
 631      }
 632  
 633     /**
 634      * Sets the request body (for POST, PUT and similar requests)
 635      *
 636      * @param    string  Request body
 637      * @access   public
 638      */
 639      function setBody($body)
 640      {
 641          $this->_body = $body;
 642      }
 643  
 644      /**
 645      * Clears any postdata that has been added (DEPRECATED).
 646      *
 647      * Useful for multiple request scenarios.
 648      *
 649      * @access public
 650      * @deprecated deprecated since 1.2
 651      */
 652      function clearPostData()
 653      {
 654          $this->_postData = null;
 655      }
 656  
 657      /**
 658      * Appends a cookie to "Cookie:" header
 659      *
 660      * @param string $name cookie name
 661      * @param string $value cookie value
 662      * @access public
 663      */
 664      function addCookie($name, $value)
 665      {
 666          $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
 667          $this->addHeader('Cookie', $cookies . $name . '=' . $value);
 668      }
 669  
 670      /**
 671      * Clears any cookies that have been added (DEPRECATED).
 672      *
 673      * Useful for multiple request scenarios
 674      *
 675      * @access public
 676      * @deprecated deprecated since 1.2
 677      */
 678      function clearCookies()
 679      {
 680          $this->removeHeader('Cookie');
 681      }
 682  
 683      /**
 684      * Sends the request
 685      *
 686      * @access public
 687      * @param  bool   Whether to store response body in Response object property,
 688      *                set this to false if downloading a LARGE file and using a Listener
 689      * @return mixed  PEAR error on error, true otherwise
 690      */
 691      function sendRequest($saveBody = true)
 692      {
 693          if (!is_a($this->_url, 'Net_URL')) {
 694              return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);
 695          }
 696  
 697          $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
 698          $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
 699  
 700          if (strcasecmp($this->_url->protocol, 'https') == 0) {
 701              // Bug #14127, don't try connecting to HTTPS sites without OpenSSL
 702              if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) {
 703                  return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests',
 704                                          HTTP_REQUEST_ERROR_URL);
 705              } elseif (isset($this->_proxy_host)) {
 706                  return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);
 707              }
 708              $host = 'ssl://' . $host;
 709          }
 710  
 711          // magic quotes may fuck up file uploads and chunked response processing
 712          $magicQuotes = ini_get('magic_quotes_runtime');
 713          ini_set('magic_quotes_runtime', false);
 714  
 715          // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
 716          // connection token to a proxy server...
 717          if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
 718              'Keep-Alive' == $this->_requestHeaders['connection'])
 719          {
 720              $this->removeHeader('connection');
 721          }
 722  
 723          $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
 724                       (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
 725          $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
 726          $sockKey   = $host . ':' . $port;
 727          unset($this->_sock);
 728  
 729          // There is a connected socket in the "static" property?
 730          if ($keepAlive && !empty($sockets[$sockKey]) &&
 731              !empty($sockets[$sockKey]->fp))
 732          {
 733              $this->_sock =& $sockets[$sockKey];
 734              $err = null;
 735          } else {
 736              $this->_notify('connect');
 737              $this->_sock =& new Net_Socket();
 738              $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
 739          }
 740          PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
 741  
 742          if (!PEAR::isError($err)) {
 743              if (!empty($this->_readTimeout)) {
 744                  $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
 745              }
 746  
 747              $this->_notify('sentRequest');
 748  
 749              // Read the response
 750              $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
 751              $err = $this->_response->process(
 752                  $this->_saveBody && $saveBody,
 753                  HTTP_REQUEST_METHOD_HEAD != $this->_method
 754              );
 755  
 756              if ($keepAlive) {
 757                  $keepAlive = (isset($this->_response->_headers['content-length'])
 758                                || (isset($this->_response->_headers['transfer-encoding'])
 759                                    && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
 760                  if ($keepAlive) {
 761                      if (isset($this->_response->_headers['connection'])) {
 762                          $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
 763                      } else {
 764                          $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
 765                      }
 766                  }
 767              }
 768          }
 769  
 770          ini_set('magic_quotes_runtime', $magicQuotes);
 771  
 772          if (PEAR::isError($err)) {
 773              return $err;
 774          }
 775  
 776          if (!$keepAlive) {
 777              $this->disconnect();
 778          // Store the connected socket in "static" property
 779          } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
 780              $sockets[$sockKey] =& $this->_sock;
 781          }
 782  
 783          // Check for redirection
 784          if (    $this->_allowRedirects
 785              AND $this->_redirects <= $this->_maxRedirects
 786              AND $this->getResponseCode() > 300
 787              AND $this->getResponseCode() < 399
 788              AND !empty($this->_response->_headers['location'])) {
 789  
 790  
 791              $redirect = $this->_response->_headers['location'];
 792  
 793              // Absolute URL
 794              if (preg_match('/^https?:\/\//i', $redirect)) {
 795                  $this->_url = &new Net_URL($redirect);
 796                  $this->addHeader('Host', $this->_generateHostHeader());
 797              // Absolute path
 798              } elseif ($redirect{0} == '/') {
 799                  $this->_url->path = $redirect;
 800  
 801              // Relative path
 802              } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
 803                  if (substr($this->_url->path, -1) == '/') {
 804                      $redirect = $this->_url->path . $redirect;
 805                  } else {
 806                      $redirect = dirname($this->_url->path) . '/' . $redirect;
 807                  }
 808                  $redirect = Net_URL::resolvePath($redirect);
 809                  $this->_url->path = $redirect;
 810  
 811              // Filename, no path
 812              } else {
 813                  if (substr($this->_url->path, -1) == '/') {
 814                      $redirect = $this->_url->path . $redirect;
 815                  } else {
 816                      $redirect = dirname($this->_url->path) . '/' . $redirect;
 817                  }
 818                  $this->_url->path = $redirect;
 819              }
 820  
 821              $this->_redirects++;
 822              return $this->sendRequest($saveBody);
 823  
 824          // Too many redirects
 825          } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
 826              return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);
 827          }
 828  
 829          return true;
 830      }
 831  
 832      /**
 833       * Disconnect the socket, if connected. Only useful if using Keep-Alive.
 834       *
 835       * @access public
 836       */
 837      function disconnect()
 838      {
 839          if (!empty($this->_sock) && !empty($this->_sock->fp)) {
 840              $this->_notify('disconnect');
 841              $this->_sock->disconnect();
 842          }
 843      }
 844  
 845      /**
 846      * Returns the response code
 847      *
 848      * @access public
 849      * @return mixed     Response code, false if not set
 850      */
 851      function getResponseCode()
 852      {
 853          return isset($this->_response->_code) ? $this->_response->_code : false;
 854      }
 855  
 856      /**
 857      * Returns the response reason phrase
 858      *
 859      * @access public
 860      * @return mixed     Response reason phrase, false if not set
 861      */
 862      function getResponseReason()
 863      {
 864          return isset($this->_response->_reason) ? $this->_response->_reason : false;
 865      }
 866  
 867      /**
 868      * Returns either the named header or all if no name given
 869      *
 870      * @access public
 871      * @param string     The header name to return, do not set to get all headers
 872      * @return mixed     either the value of $headername (false if header is not present)
 873      *                   or an array of all headers
 874      */
 875      function getResponseHeader($headername = null)
 876      {
 877          if (!isset($headername)) {
 878              return isset($this->_response->_headers)? $this->_response->_headers: array();
 879          } else {
 880              $headername = strtolower($headername);
 881              return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
 882          }
 883      }
 884  
 885      /**
 886      * Returns the body of the response
 887      *
 888      * @access public
 889      * @return mixed     response body, false if not set
 890      */
 891      function getResponseBody()
 892      {
 893          return isset($this->_response->_body) ? $this->_response->_body : false;
 894      }
 895  
 896      /**
 897      * Returns cookies set in response
 898      *
 899      * @access public
 900      * @return mixed     array of response cookies, false if none are present
 901      */
 902      function getResponseCookies()
 903      {
 904          return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
 905      }
 906  
 907      /**
 908      * Builds the request string
 909      *
 910      * @access private
 911      * @return string The request string
 912      */
 913      function _buildRequest()
 914      {
 915          $separator = ini_get('arg_separator.output');
 916          ini_set('arg_separator.output', '&');
 917          $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
 918          ini_set('arg_separator.output', $separator);
 919  
 920          $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
 921          $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
 922          $path = $this->_url->path . $querystring;
 923          $url  = $host . $port . $path;
 924  
 925          if (!strlen($url)) {
 926              $url = '/';
 927          }
 928  
 929          $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
 930  
 931          if (in_array($this->_method, $this->_bodyDisallowed) ||
 932              (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
 933               (empty($this->_postData) && empty($this->_postFiles)))))
 934          {
 935              $this->removeHeader('Content-Type');
 936          } else {
 937              if (empty($this->_requestHeaders['content-type'])) {
 938                  // Add default content-type
 939                  $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
 940              } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
 941                  $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
 942                  $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
 943              }
 944          }
 945  
 946          // Request Headers
 947          if (!empty($this->_requestHeaders)) {
 948              foreach ($this->_requestHeaders as $name => $value) {
 949                  $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
 950                  $request      .= $canonicalName . ': ' . $value . "\r\n";
 951              }
 952          }
 953  
 954          // Method does not allow a body, simply add a final CRLF
 955          if (in_array($this->_method, $this->_bodyDisallowed)) {
 956  
 957              $request .= "\r\n";
 958  
 959          // Post data if it's an array
 960          } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&
 961                    (!empty($this->_postData) || !empty($this->_postFiles))) {
 962  
 963              // "normal" POST request
 964              if (!isset($boundary)) {
 965                  $postdata = implode('&', array_map(
 966                      create_function('$a', 'return $a[0] . \'=\' . $a[1];'),
 967                      $this->_flattenArray('', $this->_postData)
 968                  ));
 969  
 970              // multipart request, probably with file uploads
 971              } else {
 972                  $postdata = '';
 973                  if (!empty($this->_postData)) {
 974                      $flatData = $this->_flattenArray('', $this->_postData);
 975                      foreach ($flatData as $item) {
 976                          $postdata .= '--' . $boundary . "\r\n";
 977                          $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
 978                          $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
 979                      }
 980                  }
 981                  foreach ($this->_postFiles as $name => $value) {
 982                      if (is_array($value['name'])) {
 983                          $varname       = $name . ($this->_useBrackets? '[]': '');
 984                      } else {
 985                          $varname       = $name;
 986                          $value['name'] = array($value['name']);
 987                      }
 988                      foreach ($value['name'] as $key => $filename) {
 989                          $fp       = fopen($filename, 'r');
 990                          $basename = basename($filename);
 991                          $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
 992  
 993                          $postdata .= '--' . $boundary . "\r\n";
 994                          $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
 995                          $postdata .= "\r\nContent-Type: " . $type;
 996                          $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n";
 997                          fclose($fp);
 998                      }
 999                  }
1000                  $postdata .= '--' . $boundary . "--\r\n";
1001              }
1002              $request .= 'Content-Length: ' .
1003                          (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .
1004                          "\r\n\r\n";
1005              $request .= $postdata;
1006  
1007          // Explicitly set request body
1008          } elseif (0 < strlen($this->_body)) {
1009  
1010              $request .= 'Content-Length: ' .
1011                          (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .
1012                          "\r\n\r\n";
1013              $request .= $this->_body;
1014  
1015          // No body: send a Content-Length header nonetheless (request #12900),
1016          // but do that only for methods that require a body (bug #14740)
1017          } else {
1018  
1019              if (in_array($this->_method, $this->_bodyRequired)) {
1020                  $request .= "Content-Length: 0\r\n";
1021              }
1022              $request .= "\r\n";
1023          }
1024  
1025          return $request;
1026      }
1027  
1028     /**
1029      * Helper function to change the (probably multidimensional) associative array
1030      * into the simple one.
1031      *
1032      * @param    string  name for item
1033      * @param    mixed   item's values
1034      * @return   array   array with the following items: array('item name', 'item value');
1035      * @access   private
1036      */
1037      function _flattenArray($name, $values)
1038      {
1039          if (!is_array($values)) {
1040              return array(array($name, $values));
1041          } else {
1042              $ret = array();
1043              foreach ($values as $k => $v) {
1044                  if (empty($name)) {
1045                      $newName = $k;
1046                  } elseif ($this->_useBrackets) {
1047                      $newName = $name . '[' . $k . ']';
1048                  } else {
1049                      $newName = $name;
1050                  }
1051                  $ret = array_merge($ret, $this->_flattenArray($newName, $v));
1052              }
1053              return $ret;
1054          }
1055      }
1056  
1057  
1058     /**
1059      * Adds a Listener to the list of listeners that are notified of
1060      * the object's events
1061      *
1062      * Events sent by HTTP_Request object
1063      * - 'connect': on connection to server
1064      * - 'sentRequest': after the request was sent
1065      * - 'disconnect': on disconnection from server
1066      *
1067      * Events sent by HTTP_Response object
1068      * - 'gotHeaders': after receiving response headers (headers are passed in $data)
1069      * - 'tick': on receiving a part of response body (the part is passed in $data)
1070      * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)
1071      * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)
1072      *
1073      * @param    HTTP_Request_Listener   listener to attach
1074      * @return   boolean                 whether the listener was successfully attached
1075      * @access   public
1076      */
1077      function attach(&$listener)
1078      {
1079          if (!is_a($listener, 'HTTP_Request_Listener')) {
1080              return false;
1081          }
1082          $this->_listeners[$listener->getId()] =& $listener;
1083          return true;
1084      }
1085  
1086  
1087     /**
1088      * Removes a Listener from the list of listeners
1089      *
1090      * @param    HTTP_Request_Listener   listener to detach
1091      * @return   boolean                 whether the listener was successfully detached
1092      * @access   public
1093      */
1094      function detach(&$listener)
1095      {
1096          if (!is_a($listener, 'HTTP_Request_Listener') ||
1097              !isset($this->_listeners[$listener->getId()])) {
1098              return false;
1099          }
1100          unset($this->_listeners[$listener->getId()]);
1101          return true;
1102      }
1103  
1104  
1105     /**
1106      * Notifies all registered listeners of an event.
1107      *
1108      * @param    string  Event name
1109      * @param    mixed   Additional data
1110      * @access   private
1111      * @see      HTTP_Request::attach()
1112      */
1113      function _notify($event, $data = null)
1114      {
1115          foreach (array_keys($this->_listeners) as $id) {
1116              $this->_listeners[$id]->update($this, $event, $data);
1117          }
1118      }
1119  }
1120  
1121  
1122  /**
1123   * Response class to complement the Request class
1124   *
1125   * @category    HTTP
1126   * @package     HTTP_Request
1127   * @author      Richard Heyes <[email protected]>
1128   * @author      Alexey Borzov <[email protected]>
1129   * @version     Release: 1.4.4
1130   */
1131  class HTTP_Response
1132  {
1133      /**
1134      * Socket object
1135      * @var Net_Socket
1136      */
1137      var $_sock;
1138  
1139      /**
1140      * Protocol
1141      * @var string
1142      */
1143      var $_protocol;
1144  
1145      /**
1146      * Return code
1147      * @var string
1148      */
1149      var $_code;
1150  
1151      /**
1152      * Response reason phrase
1153      * @var string
1154      */
1155      var $_reason;
1156  
1157      /**
1158      * Response headers
1159      * @var array
1160      */
1161      var $_headers;
1162  
1163      /**
1164      * Cookies set in response
1165      * @var array
1166      */
1167      var $_cookies;
1168  
1169      /**
1170      * Response body
1171      * @var string
1172      */
1173      var $_body = '';
1174  
1175     /**
1176      * Used by _readChunked(): remaining length of the current chunk
1177      * @var string
1178      */
1179      var $_chunkLength = 0;
1180  
1181     /**
1182      * Attached listeners
1183      * @var array
1184      */
1185      var $_listeners = array();
1186  
1187     /**
1188      * Bytes left to read from message-body
1189      * @var null|int
1190      */
1191      var $_toRead;
1192  
1193      /**
1194      * Constructor
1195      *
1196      * @param  Net_Socket    socket to read the response from
1197      * @param  array         listeners attached to request
1198      */
1199      function HTTP_Response(&$sock, &$listeners)
1200      {
1201          $this->_sock      =& $sock;
1202          $this->_listeners =& $listeners;
1203      }
1204  
1205  
1206     /**
1207      * Processes a HTTP response
1208      *
1209      * This extracts response code, headers, cookies and decodes body if it
1210      * was encoded in some way
1211      *
1212      * @access public
1213      * @param  bool      Whether to store response body in object property, set
1214      *                   this to false if downloading a LARGE file and using a Listener.
1215      *                   This is assumed to be true if body is gzip-encoded.
1216      * @param  bool      Whether the response can actually have a message-body.
1217      *                   Will be set to false for HEAD requests.
1218      * @throws PEAR_Error
1219      * @return mixed     true on success, PEAR_Error in case of malformed response
1220      */
1221      function process($saveBody = true, $canHaveBody = true)
1222      {
1223          do {
1224              $line = $this->_sock->readLine();
1225              if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {
1226                  return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);
1227              } else {
1228                  $this->_protocol = $s[1];
1229                  $this->_code     = intval($s[2]);
1230                  $this->_reason   = empty($s[3])? null: $s[3];
1231              }
1232              while ('' !== ($header = $this->_sock->readLine())) {
1233                  $this->_processHeader($header);
1234              }
1235          } while (100 == $this->_code);
1236  
1237          $this->_notify('gotHeaders', $this->_headers);
1238  
1239          // RFC 2616, section 4.4:
1240          // 1. Any response message which "MUST NOT" include a message-body ...
1241          // is always terminated by the first empty line after the header fields
1242          // 3. ... If a message is received with both a
1243          // Transfer-Encoding header field and a Content-Length header field,
1244          // the latter MUST be ignored.
1245          $canHaveBody = $canHaveBody && $this->_code >= 200 &&
1246                         $this->_code != 204 && $this->_code != 304;
1247  
1248          // If response body is present, read it and decode
1249          $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
1250          $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
1251          $hasBody = false;
1252          if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||
1253                  0 != $this->_headers['content-length']))
1254          {
1255              if ($chunked || !isset($this->_headers['content-length'])) {
1256                  $this->_toRead = null;
1257              } else {
1258                  $this->_toRead = $this->_headers['content-length'];
1259              }
1260              while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
1261                  if ($chunked) {
1262                      $data = $this->_readChunked();
1263                  } elseif (is_null($this->_toRead)) {
1264                      $data = $this->_sock->read(4096);
1265                  } else {
1266                      $data = $this->_sock->read(min(4096, $this->_toRead));
1267                      $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
1268                  }
1269                  if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) {
1270                      break;
1271                  } else {
1272                      $hasBody = true;
1273                      if ($saveBody || $gzipped) {
1274                          $this->_body .= $data;
1275                      }
1276                      $this->_notify($gzipped? 'gzTick': 'tick', $data);
1277                  }
1278              }
1279          }
1280  
1281          if ($hasBody) {
1282              // Uncompress the body if needed
1283              if ($gzipped) {
1284                  $body = $this->_decodeGzip($this->_body);
1285                  if (PEAR::isError($body)) {
1286                      return $body;
1287                  }
1288                  $this->_body = $body;
1289                  $this->_notify('gotBody', $this->_body);
1290              } else {
1291                  $this->_notify('gotBody');
1292              }
1293          }
1294          return true;
1295      }
1296  
1297  
1298     /**
1299      * Processes the response header
1300      *
1301      * @access private
1302      * @param  string    HTTP header
1303      */
1304      function _processHeader($header)
1305      {
1306          if (false === strpos($header, ':')) {
1307              return;
1308          }
1309          list($headername, $headervalue) = explode(':', $header, 2);
1310          $headername  = strtolower($headername);
1311          $headervalue = ltrim($headervalue);
1312  
1313          if ('set-cookie' != $headername) {
1314              if (isset($this->_headers[$headername])) {
1315                  $this->_headers[$headername] .= ',' . $headervalue;
1316              } else {
1317                  $this->_headers[$headername]  = $headervalue;
1318              }
1319          } else {
1320              $this->_parseCookie($headervalue);
1321          }
1322      }
1323  
1324  
1325     /**
1326      * Parse a Set-Cookie header to fill $_cookies array
1327      *
1328      * @access private
1329      * @param  string    value of Set-Cookie header
1330      */
1331      function _parseCookie($headervalue)
1332      {
1333          $cookie = array(
1334              'expires' => null,
1335              'domain'  => null,
1336              'path'    => null,
1337              'secure'  => false
1338          );
1339  
1340          // Only a name=value pair
1341          if (!strpos($headervalue, ';')) {
1342              $pos = strpos($headervalue, '=');
1343              $cookie['name']  = trim(substr($headervalue, 0, $pos));
1344              $cookie['value'] = trim(substr($headervalue, $pos + 1));
1345  
1346          // Some optional parameters are supplied
1347          } else {
1348              $elements = explode(';', $headervalue);
1349              $pos = strpos($elements[0], '=');
1350              $cookie['name']  = trim(substr($elements[0], 0, $pos));
1351              $cookie['value'] = trim(substr($elements[0], $pos + 1));
1352  
1353              for ($i = 1; $i < count($elements); $i++) {
1354                  if (false === strpos($elements[$i], '=')) {
1355                      $elName  = trim($elements[$i]);
1356                      $elValue = null;
1357                  } else {
1358                      list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
1359                  }
1360                  $elName = strtolower($elName);
1361                  if ('secure' == $elName) {
1362                      $cookie['secure'] = true;
1363                  } elseif ('expires' == $elName) {
1364                      $cookie['expires'] = str_replace('"', '', $elValue);
1365                  } elseif ('path' == $elName || 'domain' == $elName) {
1366                      $cookie[$elName] = urldecode($elValue);
1367                  } else {
1368                      $cookie[$elName] = $elValue;
1369                  }
1370              }
1371          }
1372          $this->_cookies[] = $cookie;
1373      }
1374  
1375  
1376     /**
1377      * Read a part of response body encoded with chunked Transfer-Encoding
1378      *
1379      * @access private
1380      * @return string
1381      */
1382      function _readChunked()
1383      {
1384          // at start of the next chunk?
1385          if (0 == $this->_chunkLength) {
1386              $line = $this->_sock->readLine();
1387              if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
1388                  $this->_chunkLength = hexdec($matches[1]);
1389                  // Chunk with zero length indicates the end
1390                  if (0 == $this->_chunkLength) {
1391                      $this->_sock->readLine(); // make this an eof()
1392                      return '';
1393                  }
1394              } else {
1395                  return '';
1396              }
1397          }
1398          $data = $this->_sock->read($this->_chunkLength);
1399          $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);
1400          if (0 == $this->_chunkLength) {
1401              $this->_sock->readLine(); // Trailing CRLF
1402          }
1403          return $data;
1404      }
1405  
1406  
1407     /**
1408      * Notifies all registered listeners of an event.
1409      *
1410      * @param    string  Event name
1411      * @param    mixed   Additional data
1412      * @access   private
1413      * @see HTTP_Request::_notify()
1414      */
1415      function _notify($event, $data = null)
1416      {
1417          foreach (array_keys($this->_listeners) as $id) {
1418              $this->_listeners[$id]->update($this, $event, $data);
1419          }
1420      }
1421  
1422  
1423     /**
1424      * Decodes the message-body encoded by gzip
1425      *
1426      * The real decoding work is done by gzinflate() built-in function, this
1427      * method only parses the header and checks data for compliance with
1428      * RFC 1952
1429      *
1430      * @access   private
1431      * @param    string  gzip-encoded data
1432      * @return   string  decoded data
1433      */
1434      function _decodeGzip($data)
1435      {
1436          if (HTTP_REQUEST_MBSTRING) {
1437              $oldEncoding = mb_internal_encoding();
1438              mb_internal_encoding('iso-8859-1');
1439          }
1440          $length = strlen($data);
1441          // If it doesn't look like gzip-encoded data, don't bother
1442          if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
1443              return $data;
1444          }
1445          $method = ord(substr($data, 2, 1));
1446          if (8 != $method) {
1447              return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);
1448          }
1449          $flags = ord(substr($data, 3, 1));
1450          if ($flags & 224) {
1451              return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);
1452          }
1453  
1454          // header is 10 bytes minimum. may be longer, though.
1455          $headerLength = 10;
1456          // extra fields, need to skip 'em
1457          if ($flags & 4) {
1458              if ($length - $headerLength - 2 < 8) {
1459                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1460              }
1461              $extraLength = unpack('v', substr($data, 10, 2));
1462              if ($length - $headerLength - 2 - $extraLength[1] < 8) {
1463                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1464              }
1465              $headerLength += $extraLength[1] + 2;
1466          }
1467          // file name, need to skip that
1468          if ($flags & 8) {
1469              if ($length - $headerLength - 1 < 8) {
1470                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1471              }
1472              $filenameLength = strpos(substr($data, $headerLength), chr(0));
1473              if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
1474                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1475              }
1476              $headerLength += $filenameLength + 1;
1477          }
1478          // comment, need to skip that also
1479          if ($flags & 16) {
1480              if ($length - $headerLength - 1 < 8) {
1481                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1482              }
1483              $commentLength = strpos(substr($data, $headerLength), chr(0));
1484              if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
1485                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1486              }
1487              $headerLength += $commentLength + 1;
1488          }
1489          // have a CRC for header. let's check
1490          if ($flags & 1) {
1491              if ($length - $headerLength - 2 < 8) {
1492                  return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);
1493              }
1494              $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
1495              $crcStored = unpack('v', substr($data, $headerLength, 2));
1496              if ($crcReal != $crcStored[1]) {
1497                  return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
1498              }
1499              $headerLength += 2;
1500          }
1501          // unpacked data CRC and size at the end of encoded data
1502          $tmp = unpack('V2', substr($data, -8));
1503          $dataCrc  = $tmp[1];
1504          $dataSize = $tmp[2];
1505  
1506          // finally, call the gzinflate() function
1507          // don't pass $dataSize to gzinflate, see bugs #13135, #14370
1508          $unpacked = gzinflate(substr($data, $headerLength, -8));
1509          if (false === $unpacked) {
1510              return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);
1511          } elseif ($dataSize != strlen($unpacked)) {
1512              return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);
1513          } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
1514              return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);
1515          }
1516          if (HTTP_REQUEST_MBSTRING) {
1517              mb_internal_encoding($oldEncoding);
1518          }
1519          return $unpacked;
1520      }
1521  } // End class HTTP_Response
1522  ?>


Generated: Fri Nov 28 20:08:37 2014 Cross-referenced by PHPXref 0.7.1