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