[ Index ]
 

Code source de phpMyVisites 2.3

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

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

   1  <?php
   2  // +-----------------------------------------------------------------------+

   3  // | Copyright (c) 2002-2003, Richard Heyes                                |

   4  // | All rights reserved.                                                  |

   5  // |                                                                       |

   6  // | Redistribution and use in source and binary forms, with or without    |

   7  // | modification, are permitted provided that the following conditions    |

   8  // | are met:                                                              |

   9  // |                                                                       |

  10  // | o Redistributions of source code must retain the above copyright      |

  11  // |   notice, this list of conditions and the following disclaimer.       |

  12  // | o Redistributions in binary form must reproduce the above copyright   |

  13  // |   notice, this list of conditions and the following disclaimer in the |

  14  // |   documentation and/or other materials provided with the distribution.|

  15  // | o The names of the authors may not be used to endorse or promote      |

  16  // |   products derived from this software without specific prior written  |

  17  // |   permission.                                                         |

  18  // |                                                                       |

  19  // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |

  20  // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |

  21  // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |

  22  // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |

  23  // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |

  24  // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |

  25  // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |

  26  // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |

  27  // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |

  28  // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |

  29  // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |

  30  // |                                                                       |

  31  // +-----------------------------------------------------------------------+

  32  // | Author: Richard Heyes <richard@phpguru.org>                           |

  33  // +-----------------------------------------------------------------------+

  34  //

  35  // $Id: Request.php,v 1.51 2006/10/25 16:23:31 avb Exp $

  36  //

  37  // HTTP_Request Class

  38  //

  39  // Simple example, (Fetches yahoo.com and displays it):

  40  //

  41  // $a = &new HTTP_Request('http://www.yahoo.com/');

  42  // $a->sendRequest();

  43  // echo $a->getResponseBody();

  44  //

  45  
  46  require_once 'PEAR.php';
  47  require_once 'Net/Socket.php';
  48  require_once 'Net/URL.php';
  49  
  50  define('HTTP_REQUEST_METHOD_GET',     'GET',     true);
  51  define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);
  52  define('HTTP_REQUEST_METHOD_POST',    'POST',    true);
  53  define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);
  54  define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);
  55  define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);
  56  define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);
  57  
  58  define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);
  59  define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);
  60  
  61  class HTTP_Request {
  62  
  63      /**

  64      * Instance of Net_URL

  65      * @var object Net_URL

  66      */
  67      var $_url;
  68  
  69      /**

  70      * Type of request

  71      * @var string

  72      */
  73      var $_method;
  74  
  75      /**

  76      * HTTP Version

  77      * @var string

  78      */
  79      var $_http;
  80  
  81      /**

  82      * Request headers

  83      * @var array

  84      */
  85      var $_requestHeaders;
  86  
  87      /**

  88      * Basic Auth Username

  89      * @var string

  90      */
  91      var $_user;
  92      
  93      /**

  94      * Basic Auth Password

  95      * @var string

  96      */
  97      var $_pass;
  98  
  99      /**

 100      * Socket object

 101      * @var object Net_Socket

 102      */
 103      var $_sock;
 104      
 105      /**

 106      * Proxy server

 107      * @var string

 108      */
 109      var $_proxy_host;
 110      
 111      /**

 112      * Proxy port

 113      * @var integer

 114      */
 115      var $_proxy_port;
 116      
 117      /**

 118      * Proxy username

 119      * @var string

 120      */
 121      var $_proxy_user;
 122      
 123      /**

 124      * Proxy password

 125      * @var string

 126      */
 127      var $_proxy_pass;
 128  
 129      /**

 130      * Post data

 131      * @var array

 132      */
 133      var $_postData;
 134  
 135     /**

 136      * Request body  

 137      * @var string

 138      */
 139      var $_body;
 140  
 141     /**

 142      * A list of methods that MUST NOT have a request body, per RFC 2616

 143      * @var array

 144      */
 145      var $_bodyDisallowed = array('TRACE');
 146  
 147     /**

 148      * Files to post 

 149      * @var array

 150      */
 151      var $_postFiles = array();
 152  
 153      /**

 154      * Connection timeout.

 155      * @var float

 156      */
 157      var $_timeout;
 158      
 159      /**

 160      * HTTP_Response object

 161      * @var object HTTP_Response

 162      */
 163      var $_response;
 164      
 165      /**

 166      * Whether to allow redirects

 167      * @var boolean

 168      */
 169      var $_allowRedirects;
 170      
 171      /**

 172      * Maximum redirects allowed

 173      * @var integer

 174      */
 175      var $_maxRedirects;
 176      
 177      /**

 178      * Current number of redirects

 179      * @var integer

 180      */
 181      var $_redirects;
 182  
 183     /**

 184      * Whether to append brackets [] to array variables

 185      * @var bool

 186      */
 187      var $_useBrackets = true;
 188  
 189     /**

 190      * Attached listeners

 191      * @var array

 192      */
 193      var $_listeners = array();
 194  
 195     /**

 196      * Whether to save response body in response object property  

 197      * @var bool

 198      */
 199      var $_saveBody = true;
 200  
 201     /**

 202      * Timeout for reading from socket (array(seconds, microseconds))

 203      * @var array

 204      */
 205      var $_readTimeout = null;
 206  
 207     /**

 208      * Options to pass to Net_Socket::connect. See stream_context_create

 209      * @var array

 210      */
 211      var $_socketOptions = null;
 212  
 213      /**

 214      * Constructor

 215      *

 216      * Sets up the object

 217      * @param    string  The url to fetch/access

 218      * @param    array   Associative array of parameters which can have the following keys:

 219      * <ul>

 220      *   <li>method         - Method to use, GET, POST etc (string)</li>

 221      *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>

 222      *   <li>user           - Basic Auth username (string)</li>

 223      *   <li>pass           - Basic Auth password (string)</li>

 224      *   <li>proxy_host     - Proxy server host (string)</li>

 225      *   <li>proxy_port     - Proxy server port (integer)</li>

 226      *   <li>proxy_user     - Proxy auth username (string)</li>

 227      *   <li>proxy_pass     - Proxy auth password (string)</li>

 228      *   <li>timeout        - Connection timeout in seconds (float)</li>

 229      *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>

 230      *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>

 231      *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>

 232      *   <li>saveBody       - Whether to save response body in response object property (bool)</li>

 233      *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>

 234      *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>

 235      * </ul>

 236      * @access public

 237      */
 238      function HTTP_Request($url = '', $params = array())
 239      {
 240          $this->_method         =  HTTP_REQUEST_METHOD_GET;
 241          $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;
 242          $this->_requestHeaders = array();
 243          $this->_postData       = array();
 244          $this->_body           = null;
 245  
 246          $this->_user = null;
 247          $this->_pass = null;
 248  
 249          $this->_proxy_host = null;
 250          $this->_proxy_port = null;
 251          $this->_proxy_user = null;
 252          $this->_proxy_pass = null;
 253  
 254          $this->_allowRedirects = false;
 255          $this->_maxRedirects   = 3;
 256          $this->_redirects      = 0;
 257  
 258          $this->_timeout  = null;
 259          $this->_response = null;
 260  
 261          foreach ($params as $key => $value) {
 262              $this->{'_' . $key} = $value;
 263          }
 264  
 265          if (!empty($url)) {
 266              $this->setURL($url);
 267          }
 268  
 269          // Default useragent

 270          $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');
 271  
 272          // We don't do keep-alives by default

 273          $this->addHeader('Connection', 'close');
 274  
 275          // Basic authentication

 276          if (!empty($this->_user)) {
 277              $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));
 278          }
 279  
 280          // Proxy authentication (see bug #5913)

 281          if (!empty($this->_proxy_user)) {
 282              $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));
 283          }
 284  
 285          // Use gzip encoding if possible

 286          // Avoid gzip encoding if using multibyte functions (see #1781)

 287          if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib') &&
 288              0 == (2 & ini_get('mbstring.func_overload'))) {
 289  
 290              $this->addHeader('Accept-Encoding', 'gzip');
 291          }
 292      }
 293      
 294      /**

 295      * Generates a Host header for HTTP/1.1 requests

 296      *

 297      * @access private

 298      * @return string

 299      */
 300      function _generateHostHeader()
 301      {
 302          if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {
 303              $host = $this->_url->host . ':' . $this->_url->port;
 304  
 305          } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {
 306              $host = $this->_url->host . ':' . $this->_url->port;
 307  
 308          } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {
 309              $host = $this->_url->host . ':' . $this->_url->port;
 310          
 311          } else {
 312              $host = $this->_url->host;
 313          }
 314  
 315          return $host;
 316      }
 317      
 318      /**

 319      * Resets the object to its initial state (DEPRECATED).

 320      * Takes the same parameters as the constructor.

 321      *

 322      * @param  string $url    The url to be requested

 323      * @param  array  $params Associative array of parameters

 324      *                        (see constructor for details)

 325      * @access public

 326      * @deprecated deprecated since 1.2, call the constructor if this is necessary

 327      */
 328      function reset($url, $params = array())
 329      {
 330          $this->HTTP_Request($url, $params);
 331      }
 332  
 333      /**

 334      * Sets the URL to be requested

 335      *

 336      * @param  string The url to be requested

 337      * @access public

 338      */
 339      function setURL($url)
 340      {
 341          $this->_url = &new Net_URL($url, $this->_useBrackets);
 342  
 343          if (!empty($this->_url->user) || !empty($this->_url->pass)) {
 344              $this->setBasicAuth($this->_url->user, $this->_url->pass);
 345          }
 346  
 347          if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {
 348              $this->addHeader('Host', $this->_generateHostHeader());
 349          }
 350  
 351          // set '/' instead of empty path rather than check later (see bug #8662)

 352          if (empty($this->_url->path)) {
 353              $this->_url->path = '/';
 354          } 
 355      }
 356      
 357     /**

 358      * Returns the current request URL  

 359      *

 360      * @return   string  Current request URL

 361      * @access   public

 362      */
 363      function getUrl($url)
 364      {
 365          return empty($this->_url)? '': $this->_url->getUrl();
 366      }
 367  
 368      /**

 369      * Sets a proxy to be used

 370      *

 371      * @param string     Proxy host

 372      * @param int        Proxy port

 373      * @param string     Proxy username

 374      * @param string     Proxy password

 375      * @access public

 376      */
 377      function setProxy($host, $port = 8080, $user = null, $pass = null)
 378      {
 379          $this->_proxy_host = $host;
 380          $this->_proxy_port = $port;
 381          $this->_proxy_user = $user;
 382          $this->_proxy_pass = $pass;
 383  
 384          if (!empty($user)) {
 385              $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
 386          }
 387      }
 388  
 389      /**

 390      * Sets basic authentication parameters

 391      *

 392      * @param string     Username

 393      * @param string     Password

 394      */
 395      function setBasicAuth($user, $pass)
 396      {
 397          $this->_user = $user;
 398          $this->_pass = $pass;
 399  
 400          $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));
 401      }
 402  
 403      /**

 404      * Sets the method to be used, GET, POST etc.

 405      *

 406      * @param string     Method to use. Use the defined constants for this

 407      * @access public

 408      */
 409      function setMethod($method)
 410      {
 411          $this->_method = $method;
 412      }
 413  
 414      /**

 415      * Sets the HTTP version to use, 1.0 or 1.1

 416      *

 417      * @param string     Version to use. Use the defined constants for this

 418      * @access public

 419      */
 420      function setHttpVer($http)
 421      {
 422          $this->_http = $http;
 423      }
 424  
 425      /**

 426      * Adds a request header

 427      *

 428      * @param string     Header name

 429      * @param string     Header value

 430      * @access public

 431      */
 432      function addHeader($name, $value)
 433      {
 434          $this->_requestHeaders[strtolower($name)] = $value;
 435      }
 436  
 437      /**

 438      * Removes a request header

 439      *

 440      * @param string     Header name to remove

 441      * @access public

 442      */
 443      function removeHeader($name)
 444      {
 445          if (isset($this->_requestHeaders[strtolower($name)])) {
 446              unset($this->_requestHeaders[strtolower($name)]);
 447          }
 448      }
 449  
 450      /**

 451      * Adds a querystring parameter

 452      *

 453      * @param string     Querystring parameter name

 454      * @param string     Querystring parameter value

 455      * @param bool       Whether the value is already urlencoded or not, default = not

 456      * @access public

 457      */
 458      function addQueryString($name, $value, $preencoded = false)
 459      {
 460          $this->_url->addQueryString($name, $value, $preencoded);
 461      }    
 462      
 463      /**

 464      * Sets the querystring to literally what you supply

 465      *

 466      * @param string     The querystring data. Should be of the format foo=bar&x=y etc

 467      * @param bool       Whether data is already urlencoded or not, default = already encoded

 468      * @access public

 469      */
 470      function addRawQueryString($querystring, $preencoded = true)
 471      {
 472          $this->_url->addRawQueryString($querystring, $preencoded);
 473      }
 474  
 475      /**

 476      * Adds postdata items

 477      *

 478      * @param string     Post data name

 479      * @param string     Post data value

 480      * @param bool       Whether data is already urlencoded or not, default = not

 481      * @access public

 482      */
 483      function addPostData($name, $value, $preencoded = false)
 484      {
 485          if ($preencoded) {
 486              $this->_postData[$name] = $value;
 487          } else {
 488              $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);
 489          }
 490      }
 491  
 492     /**

 493      * Recursively applies the callback function to the value

 494      * 

 495      * @param    mixed   Callback function

 496      * @param    mixed   Value to process

 497      * @access   private

 498      * @return   mixed   Processed value

 499      */
 500      function _arrayMapRecursive($callback, $value)
 501      {
 502          if (!is_array($value)) {
 503              return call_user_func($callback, $value);
 504          } else {
 505              $map = array();
 506              foreach ($value as $k => $v) {
 507                  $map[$k] = $this->_arrayMapRecursive($callback, $v);
 508              }
 509              return $map;
 510          }
 511      }
 512  
 513     /**

 514      * Adds a file to upload

 515      * 

 516      * This also changes content-type to 'multipart/form-data' for proper upload

 517      * 

 518      * @access public

 519      * @param  string    name of file-upload field

 520      * @param  mixed     file name(s)

 521      * @param  mixed     content-type(s) of file(s) being uploaded

 522      * @return bool      true on success

 523      * @throws PEAR_Error

 524      */
 525      function addFile($inputName, $fileName, $contentType = 'application/octet-stream')
 526      {
 527          if (!is_array($fileName) && !is_readable($fileName)) {
 528              return PEAR::raiseError("File '{$fileName}' is not readable");
 529          } elseif (is_array($fileName)) {
 530              foreach ($fileName as $name) {
 531                  if (!is_readable($name)) {
 532                      return PEAR::raiseError("File '{$name}' is not readable");
 533                  }
 534              }
 535          }
 536          $this->addHeader('Content-Type', 'multipart/form-data');
 537          $this->_postFiles[$inputName] = array(
 538              'name' => $fileName,
 539              'type' => $contentType
 540          );
 541          return true;
 542      }
 543  
 544      /**

 545      * Adds raw postdata (DEPRECATED)

 546      *

 547      * @param string     The data

 548      * @param bool       Whether data is preencoded or not, default = already encoded

 549      * @access public

 550      * @deprecated       deprecated since 1.3.0, method setBody() should be used instead

 551      */
 552      function addRawPostData($postdata, $preencoded = true)
 553      {
 554          $this->_body = $preencoded ? $postdata : urlencode($postdata);
 555      }
 556  
 557     /**

 558      * Sets the request body (for POST, PUT and similar requests)

 559      *

 560      * @param    string  Request body

 561      * @access   public

 562      */
 563      function setBody($body)
 564      {
 565          $this->_body = $body;
 566      }
 567  
 568      /**

 569      * Clears any postdata that has been added (DEPRECATED). 

 570      * 

 571      * Useful for multiple request scenarios.

 572      *

 573      * @access public

 574      * @deprecated deprecated since 1.2

 575      */
 576      function clearPostData()
 577      {
 578          $this->_postData = null;
 579      }
 580  
 581      /**

 582      * Appends a cookie to "Cookie:" header

 583      * 

 584      * @param string $name cookie name

 585      * @param string $value cookie value

 586      * @access public

 587      */
 588      function addCookie($name, $value)
 589      {
 590          $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';
 591          $this->addHeader('Cookie', $cookies . $name . '=' . $value);
 592      }
 593      
 594      /**

 595      * Clears any cookies that have been added (DEPRECATED). 

 596      * 

 597      * Useful for multiple request scenarios

 598      *

 599      * @access public

 600      * @deprecated deprecated since 1.2

 601      */
 602      function clearCookies()
 603      {
 604          $this->removeHeader('Cookie');
 605      }
 606  
 607      /**

 608      * Sends the request

 609      *

 610      * @access public

 611      * @param  bool   Whether to store response body in Response object property,

 612      *                set this to false if downloading a LARGE file and using a Listener

 613      * @return mixed  PEAR error on error, true otherwise

 614      */
 615      function sendRequest($saveBody = true)
 616      {
 617          if (!is_a($this->_url, 'Net_URL')) {
 618              return PEAR::raiseError('No URL given.');
 619          }
 620  
 621          $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;
 622          $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;
 623  
 624          // 4.3.0 supports SSL connections using OpenSSL. The function test determines

 625          // we running on at least 4.3.0

 626          if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) {
 627              if (isset($this->_proxy_host)) {
 628                  return PEAR::raiseError('HTTPS proxies are not supported.');
 629              }
 630              $host = 'ssl://' . $host;
 631          }
 632  
 633          // magic quotes may fuck up file uploads and chunked response processing

 634          $magicQuotes = ini_get('magic_quotes_runtime');
 635          ini_set('magic_quotes_runtime', false);
 636  
 637          // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive 

 638          // connection token to a proxy server...

 639          if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&
 640              'Keep-Alive' == $this->_requestHeaders['connection'])
 641          {
 642              $this->removeHeader('connection');
 643          }
 644  
 645          $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||
 646                       (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);
 647          $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');
 648          $sockKey   = $host . ':' . $port;
 649          unset($this->_sock);
 650  
 651          // There is a connected socket in the "static" property?

 652          if ($keepAlive && !empty($sockets[$sockKey]) &&
 653              !empty($sockets[$sockKey]->fp)) 
 654          {
 655              $this->_sock =& $sockets[$sockKey];
 656              $err = null;
 657          } else {
 658              $this->_notify('connect');
 659              $this->_sock =& new Net_Socket();
 660              $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);
 661          }
 662          PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());
 663  
 664          if (!PEAR::isError($err)) {
 665              if (!empty($this->_readTimeout)) {
 666                  $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);
 667              }
 668  
 669              $this->_notify('sentRequest');
 670  
 671              // Read the response

 672              $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);
 673              $err = $this->_response->process(
 674                  $this->_saveBody && $saveBody,
 675                  HTTP_REQUEST_METHOD_HEAD != $this->_method
 676              );
 677  
 678              if ($keepAlive) {
 679                  $keepAlive = (isset($this->_response->_headers['content-length'])
 680                                || (isset($this->_response->_headers['transfer-encoding'])
 681                                    && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));
 682                  if ($keepAlive) {
 683                      if (isset($this->_response->_headers['connection'])) {
 684                          $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';
 685                      } else {
 686                          $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;
 687                      }
 688                  }
 689              }
 690          }
 691  
 692          ini_set('magic_quotes_runtime', $magicQuotes);
 693  
 694          if (PEAR::isError($err)) {
 695              return $err;
 696          }
 697  
 698          if (!$keepAlive) {
 699              $this->disconnect();
 700          // Store the connected socket in "static" property

 701          } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {
 702              $sockets[$sockKey] =& $this->_sock;
 703          }
 704  
 705          // Check for redirection

 706          if (    $this->_allowRedirects
 707              AND $this->_redirects <= $this->_maxRedirects
 708              AND $this->getResponseCode() > 300
 709              AND $this->getResponseCode() < 399
 710              AND !empty($this->_response->_headers['location'])) {
 711  
 712              
 713              $redirect = $this->_response->_headers['location'];
 714  
 715              // Absolute URL

 716              if (preg_match('/^https?:\/\//i', $redirect)) {
 717                  $this->_url = &new Net_URL($redirect);
 718                  $this->addHeader('Host', $this->_generateHostHeader());
 719              // Absolute path

 720              } elseif ($redirect{0} == '/') {
 721                  $this->_url->path = $redirect;
 722              
 723              // Relative path

 724              } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {
 725                  if (substr($this->_url->path, -1) == '/') {
 726                      $redirect = $this->_url->path . $redirect;
 727                  } else {
 728                      $redirect = dirname($this->_url->path) . '/' . $redirect;
 729                  }
 730                  $redirect = Net_URL::resolvePath($redirect);
 731                  $this->_url->path = $redirect;
 732                  
 733              // Filename, no path

 734              } else {
 735                  if (substr($this->_url->path, -1) == '/') {
 736                      $redirect = $this->_url->path . $redirect;
 737                  } else {
 738                      $redirect = dirname($this->_url->path) . '/' . $redirect;
 739                  }
 740                  $this->_url->path = $redirect;
 741              }
 742  
 743              $this->_redirects++;
 744              return $this->sendRequest($saveBody);
 745  
 746          // Too many redirects

 747          } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {
 748              return PEAR::raiseError('Too many redirects');
 749          }
 750  
 751          return true;
 752      }
 753  
 754      /**

 755       * Disconnect the socket, if connected. Only useful if using Keep-Alive.

 756       *

 757       * @access public

 758       */
 759      function disconnect()
 760      {
 761          if (!empty($this->_sock) && !empty($this->_sock->fp)) {
 762              $this->_notify('disconnect');
 763              $this->_sock->disconnect();
 764          }
 765      }
 766  
 767      /**

 768      * Returns the response code

 769      *

 770      * @access public

 771      * @return mixed     Response code, false if not set

 772      */
 773      function getResponseCode()
 774      {
 775          return isset($this->_response->_code) ? $this->_response->_code : false;
 776      }
 777  
 778      /**

 779      * Returns either the named header or all if no name given

 780      *

 781      * @access public

 782      * @param string     The header name to return, do not set to get all headers

 783      * @return mixed     either the value of $headername (false if header is not present)

 784      *                   or an array of all headers

 785      */
 786      function getResponseHeader($headername = null)
 787      {
 788          if (!isset($headername)) {
 789              return isset($this->_response->_headers)? $this->_response->_headers: array();
 790          } else {
 791              $headername = strtolower($headername);
 792              return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;
 793          }
 794      }
 795  
 796      /**

 797      * Returns the body of the response

 798      *

 799      * @access public

 800      * @return mixed     response body, false if not set

 801      */
 802      function getResponseBody()
 803      {
 804          return isset($this->_response->_body) ? $this->_response->_body : false;
 805      }
 806  
 807      /**

 808      * Returns cookies set in response

 809      * 

 810      * @access public

 811      * @return mixed     array of response cookies, false if none are present

 812      */
 813      function getResponseCookies()
 814      {
 815          return isset($this->_response->_cookies) ? $this->_response->_cookies : false;
 816      }
 817  
 818      /**

 819      * Builds the request string

 820      *

 821      * @access private

 822      * @return string The request string

 823      */
 824      function _buildRequest()
 825      {
 826          $separator = ini_get('arg_separator.output');
 827          ini_set('arg_separator.output', '&');
 828          $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';
 829          ini_set('arg_separator.output', $separator);
 830  
 831          $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';
 832          $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';
 833          $path = $this->_url->path . $querystring;
 834          $url  = $host . $port . $path;
 835  
 836          $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";
 837  
 838          if (in_array($this->_method, $this->_bodyDisallowed) ||
 839              (empty($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||
 840               (empty($this->_postData) && empty($this->_postFiles)))))
 841          {
 842              $this->removeHeader('Content-Type');
 843          } else {
 844              if (empty($this->_requestHeaders['content-type'])) {
 845                  // Add default content-type

 846                  $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');
 847              } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {
 848                  $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());
 849                  $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);
 850              }
 851          }
 852  
 853          // Request Headers

 854          if (!empty($this->_requestHeaders)) {
 855              foreach ($this->_requestHeaders as $name => $value) {
 856                  $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
 857                  $request      .= $canonicalName . ': ' . $value . "\r\n";
 858              }
 859          }
 860  
 861          // No post data or wrong method, so simply add a final CRLF

 862          if (in_array($this->_method, $this->_bodyDisallowed) || 
 863              (HTTP_REQUEST_METHOD_POST != $this->_method && empty($this->_body))) {
 864  
 865              $request .= "\r\n";
 866  
 867          // Post data if it's an array

 868          } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && 
 869                    (!empty($this->_postData) || !empty($this->_postFiles))) {
 870  
 871              // "normal" POST request

 872              if (!isset($boundary)) {
 873                  $postdata = implode('&', array_map(
 874                      create_function('$a', 'return $a[0] . \'=\' . $a[1];'), 
 875                      $this->_flattenArray('', $this->_postData)
 876                  ));
 877  
 878              // multipart request, probably with file uploads

 879              } else {
 880                  $postdata = '';
 881                  if (!empty($this->_postData)) {
 882                      $flatData = $this->_flattenArray('', $this->_postData);
 883                      foreach ($flatData as $item) {
 884                          $postdata .= '--' . $boundary . "\r\n";
 885                          $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';
 886                          $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";
 887                      }
 888                  }
 889                  foreach ($this->_postFiles as $name => $value) {
 890                      if (is_array($value['name'])) {
 891                          $varname       = $name . ($this->_useBrackets? '[]': '');
 892                      } else {
 893                          $varname       = $name;
 894                          $value['name'] = array($value['name']);
 895                      }
 896                      foreach ($value['name'] as $key => $filename) {
 897                          $fp   = fopen($filename, 'r');
 898                          $data = fread($fp, filesize($filename));
 899                          fclose($fp);
 900                          $basename = basename($filename);
 901                          $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];
 902  
 903                          $postdata .= '--' . $boundary . "\r\n";
 904                          $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';
 905                          $postdata .= "\r\nContent-Type: " . $type;
 906                          $postdata .= "\r\n\r\n" . $data . "\r\n";
 907                      }
 908                  }
 909                  $postdata .= '--' . $boundary . "--\r\n";
 910              }
 911              $request .= 'Content-Length: ' . strlen($postdata) . "\r\n\r\n";
 912              $request .= $postdata;
 913  
 914          // Explicitly set request body

 915          } elseif (!empty($this->_body)) {
 916  
 917              $request .= 'Content-Length: ' . strlen($this->_body) . "\r\n\r\n";
 918              $request .= $this->_body;
 919          }
 920          
 921          return $request;
 922      }
 923  
 924     /**

 925      * Helper function to change the (probably multidimensional) associative array

 926      * into the simple one.

 927      *

 928      * @param    string  name for item

 929      * @param    mixed   item's values

 930      * @return   array   array with the following items: array('item name', 'item value');

 931      */
 932      function _flattenArray($name, $values)
 933      {
 934          if (!is_array($values)) {
 935              return array(array($name, $values));
 936          } else {
 937              $ret = array();
 938              foreach ($values as $k => $v) {
 939                  if (empty($name)) {
 940                      $newName = $k;
 941                  } elseif ($this->_useBrackets) {
 942                      $newName = $name . '[' . $k . ']';
 943                  } else {
 944                      $newName = $name;
 945                  }
 946                  $ret = array_merge($ret, $this->_flattenArray($newName, $v));
 947              }
 948              return $ret;
 949          }
 950      }
 951  
 952  
 953     /**

 954      * Adds a Listener to the list of listeners that are notified of

 955      * the object's events

 956      * 

 957      * @param    object   HTTP_Request_Listener instance to attach

 958      * @return   boolean  whether the listener was successfully attached

 959      * @access   public

 960      */
 961      function attach(&$listener)
 962      {
 963          if (!is_a($listener, 'HTTP_Request_Listener')) {
 964              return false;
 965          }
 966          $this->_listeners[$listener->getId()] =& $listener;
 967          return true;
 968      }
 969  
 970  
 971     /**

 972      * Removes a Listener from the list of listeners 

 973      * 

 974      * @param    object   HTTP_Request_Listener instance to detach

 975      * @return   boolean  whether the listener was successfully detached

 976      * @access   public

 977      */
 978      function detach(&$listener)
 979      {
 980          if (!is_a($listener, 'HTTP_Request_Listener') || 
 981              !isset($this->_listeners[$listener->getId()])) {
 982              return false;
 983          }
 984          unset($this->_listeners[$listener->getId()]);
 985          return true;
 986      }
 987  
 988  
 989     /**

 990      * Notifies all registered listeners of an event.

 991      * 

 992      * Events sent by HTTP_Request object

 993      * - 'connect': on connection to server

 994      * - 'sentRequest': after the request was sent

 995      * - 'disconnect': on disconnection from server

 996      * 

 997      * Events sent by HTTP_Response object

 998      * - 'gotHeaders': after receiving response headers (headers are passed in $data)

 999      * - 'tick': on receiving a part of response body (the part is passed in $data)

1000      * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)

1001      * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)

1002      * 

1003      * @param    string  Event name

1004      * @param    mixed   Additional data

1005      * @access   private

1006      */
1007      function _notify($event, $data = null)
1008      {
1009          foreach (array_keys($this->_listeners) as $id) {
1010              $this->_listeners[$id]->update($this, $event, $data);
1011          }
1012      }
1013  }
1014  
1015  
1016  /**

1017  * Response class to complement the Request class

1018  */
1019  class HTTP_Response
1020  {
1021      /**

1022      * Socket object

1023      * @var object

1024      */
1025      var $_sock;
1026  
1027      /**

1028      * Protocol

1029      * @var string

1030      */
1031      var $_protocol;
1032      
1033      /**

1034      * Return code

1035      * @var string

1036      */
1037      var $_code;
1038      
1039      /**

1040      * Response headers

1041      * @var array

1042      */
1043      var $_headers;
1044  
1045      /**

1046      * Cookies set in response  

1047      * @var array

1048      */
1049      var $_cookies;
1050  
1051      /**

1052      * Response body

1053      * @var string

1054      */
1055      var $_body = '';
1056  
1057     /**

1058      * Used by _readChunked(): remaining length of the current chunk

1059      * @var string

1060      */
1061      var $_chunkLength = 0;
1062  
1063     /**

1064      * Attached listeners

1065      * @var array

1066      */
1067      var $_listeners = array();
1068  
1069     /**

1070      * Bytes left to read from message-body

1071      * @var null|int

1072      */
1073      var $_toRead;
1074  
1075      /**

1076      * Constructor

1077      *

1078      * @param  object Net_Socket     socket to read the response from

1079      * @param  array                 listeners attached to request

1080      * @return mixed PEAR Error on error, true otherwise

1081      */
1082      function HTTP_Response(&$sock, &$listeners)
1083      {
1084          $this->_sock      =& $sock;
1085          $this->_listeners =& $listeners;
1086      }
1087  
1088  
1089     /**

1090      * Processes a HTTP response

1091      * 

1092      * This extracts response code, headers, cookies and decodes body if it 

1093      * was encoded in some way

1094      *

1095      * @access public

1096      * @param  bool      Whether to store response body in object property, set

1097      *                   this to false if downloading a LARGE file and using a Listener.

1098      *                   This is assumed to be true if body is gzip-encoded.

1099      * @param  bool      Whether the response can actually have a message-body.

1100      *                   Will be set to false for HEAD requests.

1101      * @throws PEAR_Error

1102      * @return mixed     true on success, PEAR_Error in case of malformed response

1103      */
1104      function process($saveBody = true, $canHaveBody = true)
1105      {
1106          do {
1107              $line = $this->_sock->readLine();
1108              if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) {
1109                  return PEAR::raiseError('Malformed response.');
1110              } else {
1111                  $this->_protocol = 'HTTP/' . $http_version;
1112                  $this->_code     = intval($returncode);
1113              }
1114              while ('' !== ($header = $this->_sock->readLine())) {
1115                  $this->_processHeader($header);
1116              }
1117          } while (100 == $this->_code);
1118  
1119          $this->_notify('gotHeaders', $this->_headers);
1120  
1121          // RFC 2616, section 4.4:

1122          // 1. Any response message which "MUST NOT" include a message-body ... 

1123          // is always terminated by the first empty line after the header fields 

1124          // 3. ... If a message is received with both a

1125          // Transfer-Encoding header field and a Content-Length header field,

1126          // the latter MUST be ignored.

1127          $canHaveBody = $canHaveBody && $this->_code >= 200 && 
1128                         $this->_code != 204 && $this->_code != 304;
1129  
1130          // If response body is present, read it and decode

1131          $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);
1132          $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);
1133          $hasBody = false;
1134          if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) || 
1135                  0 != $this->_headers['content-length']))
1136          {
1137              if ($chunked || !isset($this->_headers['content-length'])) {
1138                  $this->_toRead = null;
1139              } else {
1140                  $this->_toRead = $this->_headers['content-length'];
1141              }
1142              while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {
1143                  if ($chunked) {
1144                      $data = $this->_readChunked();
1145                  } elseif (is_null($this->_toRead)) {
1146                      $data = $this->_sock->read(4096);
1147                  } else {
1148                      $data = $this->_sock->read(min(4096, $this->_toRead));
1149                      $this->_toRead -= strlen($data);
1150                  }
1151                  if ('' == $data) {
1152                      break;
1153                  } else {
1154                      $hasBody = true;
1155                      if ($saveBody || $gzipped) {
1156                          $this->_body .= $data;
1157                      }
1158                      $this->_notify($gzipped? 'gzTick': 'tick', $data);
1159                  }
1160              }
1161          }
1162  
1163          if ($hasBody) {
1164              // Uncompress the body if needed

1165              if ($gzipped) {
1166                  $body = $this->_decodeGzip($this->_body);
1167                  if (PEAR::isError($body)) {
1168                      return $body;
1169                  }
1170                  $this->_body = $body;
1171                  $this->_notify('gotBody', $this->_body);
1172              } else {
1173                  $this->_notify('gotBody');
1174              }
1175          }
1176          return true;
1177      }
1178  
1179  
1180     /**

1181      * Processes the response header

1182      *

1183      * @access private

1184      * @param  string    HTTP header

1185      */
1186      function _processHeader($header)
1187      {
1188          if (false === strpos($header, ':')) {
1189              return;
1190          }
1191          list($headername, $headervalue) = explode(':', $header, 2);
1192          $headername  = strtolower($headername);
1193          $headervalue = ltrim($headervalue);
1194          
1195          if ('set-cookie' != $headername) {
1196              if (isset($this->_headers[$headername])) {
1197                  $this->_headers[$headername] .= ',' . $headervalue;
1198              } else {
1199                  $this->_headers[$headername]  = $headervalue;
1200              }
1201          } else {
1202              $this->_parseCookie($headervalue);
1203          }
1204      }
1205  
1206  
1207     /**

1208      * Parse a Set-Cookie header to fill $_cookies array

1209      *

1210      * @access private

1211      * @param  string    value of Set-Cookie header

1212      */
1213      function _parseCookie($headervalue)
1214      {
1215          $cookie = array(
1216              'expires' => null,
1217              'domain'  => null,
1218              'path'    => null,
1219              'secure'  => false
1220          );
1221  
1222          // Only a name=value pair

1223          if (!strpos($headervalue, ';')) {
1224              $pos = strpos($headervalue, '=');
1225              $cookie['name']  = trim(substr($headervalue, 0, $pos));
1226              $cookie['value'] = trim(substr($headervalue, $pos + 1));
1227  
1228          // Some optional parameters are supplied

1229          } else {
1230              $elements = explode(';', $headervalue);
1231              $pos = strpos($elements[0], '=');
1232              $cookie['name']  = trim(substr($elements[0], 0, $pos));
1233              $cookie['value'] = trim(substr($elements[0], $pos + 1));
1234  
1235              for ($i = 1; $i < count($elements); $i++) {
1236                  if (false === strpos($elements[$i], '=')) {
1237                      $elName  = trim($elements[$i]);
1238                      $elValue = null;
1239                  } else {
1240                      list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
1241                  }
1242                  $elName = strtolower($elName);
1243                  if ('secure' == $elName) {
1244                      $cookie['secure'] = true;
1245                  } elseif ('expires' == $elName) {
1246                      $cookie['expires'] = str_replace('"', '', $elValue);
1247                  } elseif ('path' == $elName || 'domain' == $elName) {
1248                      $cookie[$elName] = urldecode($elValue);
1249                  } else {
1250                      $cookie[$elName] = $elValue;
1251                  }
1252              }
1253          }
1254          $this->_cookies[] = $cookie;
1255      }
1256  
1257  
1258     /**

1259      * Read a part of response body encoded with chunked Transfer-Encoding

1260      * 

1261      * @access private

1262      * @return string

1263      */
1264      function _readChunked()
1265      {
1266          // at start of the next chunk?

1267          if (0 == $this->_chunkLength) {
1268              $line = $this->_sock->readLine();
1269              if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
1270                  $this->_chunkLength = hexdec($matches[1]); 
1271                  // Chunk with zero length indicates the end

1272                  if (0 == $this->_chunkLength) {
1273                      $this->_sock->readLine(); // make this an eof()

1274                      return '';
1275                  }
1276              } else {
1277                  return '';
1278              }
1279          }
1280          $data = $this->_sock->read($this->_chunkLength);
1281          $this->_chunkLength -= strlen($data);
1282          if (0 == $this->_chunkLength) {
1283              $this->_sock->readLine(); // Trailing CRLF

1284          }
1285          return $data;
1286      }
1287  
1288  
1289     /**

1290      * Notifies all registered listeners of an event.

1291      * 

1292      * @param    string  Event name

1293      * @param    mixed   Additional data

1294      * @access   private

1295      * @see HTTP_Request::_notify()

1296      */
1297      function _notify($event, $data = null)
1298      {
1299          foreach (array_keys($this->_listeners) as $id) {
1300              $this->_listeners[$id]->update($this, $event, $data);
1301          }
1302      }
1303  
1304  
1305     /**

1306      * Decodes the message-body encoded by gzip

1307      *

1308      * The real decoding work is done by gzinflate() built-in function, this

1309      * method only parses the header and checks data for compliance with

1310      * RFC 1952  

1311      *

1312      * @access   private

1313      * @param    string  gzip-encoded data

1314      * @return   string  decoded data

1315      */
1316      function _decodeGzip($data)
1317      {
1318          $length = strlen($data);
1319          // If it doesn't look like gzip-encoded data, don't bother

1320          if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
1321              return $data;
1322          }
1323          $method = ord(substr($data, 2, 1));
1324          if (8 != $method) {
1325              return PEAR::raiseError('_decodeGzip(): unknown compression method');
1326          }
1327          $flags = ord(substr($data, 3, 1));
1328          if ($flags & 224) {
1329              return PEAR::raiseError('_decodeGzip(): reserved bits are set');
1330          }
1331  
1332          // header is 10 bytes minimum. may be longer, though.

1333          $headerLength = 10;
1334          // extra fields, need to skip 'em

1335          if ($flags & 4) {
1336              if ($length - $headerLength - 2 < 8) {
1337                  return PEAR::raiseError('_decodeGzip(): data too short');
1338              }
1339              $extraLength = unpack('v', substr($data, 10, 2));
1340              if ($length - $headerLength - 2 - $extraLength[1] < 8) {
1341                  return PEAR::raiseError('_decodeGzip(): data too short');
1342              }
1343              $headerLength += $extraLength[1] + 2;
1344          }
1345          // file name, need to skip that

1346          if ($flags & 8) {
1347              if ($length - $headerLength - 1 < 8) {
1348                  return PEAR::raiseError('_decodeGzip(): data too short');
1349              }
1350              $filenameLength = strpos(substr($data, $headerLength), chr(0));
1351              if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
1352                  return PEAR::raiseError('_decodeGzip(): data too short');
1353              }
1354              $headerLength += $filenameLength + 1;
1355          }
1356          // comment, need to skip that also

1357          if ($flags & 16) {
1358              if ($length - $headerLength - 1 < 8) {
1359                  return PEAR::raiseError('_decodeGzip(): data too short');
1360              }
1361              $commentLength = strpos(substr($data, $headerLength), chr(0));
1362              if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
1363                  return PEAR::raiseError('_decodeGzip(): data too short');
1364              }
1365              $headerLength += $commentLength + 1;
1366          }
1367          // have a CRC for header. let's check

1368          if ($flags & 1) {
1369              if ($length - $headerLength - 2 < 8) {
1370                  return PEAR::raiseError('_decodeGzip(): data too short');
1371              }
1372              $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
1373              $crcStored = unpack('v', substr($data, $headerLength, 2));
1374              if ($crcReal != $crcStored[1]) {
1375                  return PEAR::raiseError('_decodeGzip(): header CRC check failed');
1376              }
1377              $headerLength += 2;
1378          }
1379          // unpacked data CRC and size at the end of encoded data

1380          $tmp = unpack('V2', substr($data, -8));
1381          $dataCrc  = $tmp[1];
1382          $dataSize = $tmp[2];
1383  
1384          // finally, call the gzinflate() function

1385          $unpacked = @gzinflate(substr($data, $headerLength, -8), $dataSize);
1386          if (false === $unpacked) {
1387              return PEAR::raiseError('_decodeGzip(): gzinflate() call failed');
1388          } elseif ($dataSize != strlen($unpacked)) {
1389              return PEAR::raiseError('_decodeGzip(): data size check failed');
1390          } elseif ($dataCrc != crc32($unpacked)) {
1391              return PEAR::raiseError('_decodeGzip(): data CRC check failed');
1392          }
1393          return $unpacked;
1394      }
1395  } // End class HTTP_Response

1396  ?>


Généré le : Mon Nov 26 14:10:01 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics