[ Index ] |
|
Code source de phpMyVisites 2.3 |
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 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Mon Nov 26 14:10:01 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |