Ticket #4779: 4779.r8620.diff
File 4779.r8620.diff, 22.0 KB (added by , 16 years ago) |
---|
-
http.php
137 137 /** 138 138 * Send a HTTP request to a URI. 139 139 * 140 * The body and headers are part of the arguments. The 'body' argument is 141 * for the body and will accept either a string or an array. The 'headers' 142 * argument should be an array, but a string is acceptable. If the 'body' 143 * argument is an array, then it will automatically be escaped using 144 * http_build_query(). 145 * 140 146 * The only URI that are supported in the HTTP Transport implementation are 141 147 * the HTTP and HTTPS protocols. HTTP and HTTPS are assumed so the server 142 148 * might not know how to handle the send headers. Other protocols are … … 171 177 * 172 178 * @param string $url URI resource. 173 179 * @param str|array $args Optional. Override the defaults. 174 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.175 * @param string $body Optional. The body that should be sent. Will be automatically escaped and processed.176 180 * @return boolean 177 181 */ 178 function request( $url, $args = array(), $headers = null, $body = null) {182 function request( $url, $args = array() ) { 179 183 global $wp_version; 180 184 181 185 $defaults = array( 182 186 'method' => 'GET', 'timeout' => apply_filters('http_request_timeout', 3), 183 187 'redirection' => 5, 'httpversion' => '1.0', 184 188 'user-agent' => apply_filters('http_headers_useragent', 'WordPress/' . $wp_version ), 185 'blocking' => true 189 'blocking' => true, 190 'headers' => array(), 'body' => null 186 191 ); 187 192 188 193 $r = wp_parse_args( $args, $defaults ); 189 194 190 if ( is_null( $headers) )191 $ headers= array();195 if ( is_null( $r['headers'] ) ) 196 $r['headers'] = array(); 192 197 193 198 if ( ! is_array($headers) ) { 194 199 $processedHeaders = WP_Http::processHeaders($headers); … … 196 201 } 197 202 198 203 if ( isset($headers['User-Agent']) ) { 199 $ headers['user-agent'] = $headers['User-Agent'];204 $r['user-agent'] = $headers['User-Agent']; 200 205 unset($headers['User-Agent']); 201 206 } 202 207 203 if ( !isset($headers['user-agent']) )204 $ headers['user-agent'] = $r['user-agent'];208 if ( isset($headers['user-agent']) ) 209 $r['user-agent'] = $headers['user-agent']; 205 210 206 if ( is_null($ body) ) {211 if ( is_null($r['body']) ) { 207 212 $transports = WP_Http::_getTransport(); 208 213 } else { 209 if ( is_array( $body) || is_object($body) )210 $ body = http_build_query($body);214 if ( is_array( $r['body'] ) || is_object( $r['body'] ) ) 215 $r['body'] = http_build_query($r['body']); 211 216 212 217 $transports = WP_Http::_postTransport(); 213 218 } 214 219 215 220 $response = array( 'headers' => array(), 'body' => '', 'response' => array('code', 'message') ); 216 221 foreach( (array) $transports as $transport ) { 217 $response = $transport->request($url, $r , $headers, $body);222 $response = $transport->request($url, $r); 218 223 219 224 if( !is_wp_error($response) ) 220 225 return $response; … … 233 238 * 234 239 * @param string $url URI resource. 235 240 * @param str|array $args Optional. Override the defaults. 236 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.237 * @param string $body Optional. The body that should be sent. Expected to be already processed.238 241 * @return boolean 239 242 */ 240 function post($url, $args = array() , $headers = null, $body = null) {243 function post($url, $args = array()) { 241 244 $defaults = array('method' => 'POST'); 242 245 $r = wp_parse_args( $args, $defaults ); 243 return $this->request($url, $r , $headers, $body);246 return $this->request($url, $r); 244 247 } 245 248 246 249 /** … … 253 256 * 254 257 * @param string $url URI resource. 255 258 * @param str|array $args Optional. Override the defaults. 256 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.257 * @param string $body Optional. The body that should be sent. Expected to be already processed.258 259 * @return boolean 259 260 */ 260 function get($url, $args = array() , $headers = null, $body = null) {261 function get($url, $args = array()) { 261 262 $defaults = array('method' => 'GET'); 262 263 $r = wp_parse_args( $args, $defaults ); 263 return $this->request($url, $r , $headers, $body);264 return $this->request($url, $r); 264 265 } 265 266 266 267 /** … … 273 274 * 274 275 * @param string $url URI resource. 275 276 * @param str|array $args Optional. Override the defaults. 276 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.277 * @param string $body Optional. The body that should be sent. Expected to be already processed.278 277 * @return boolean 279 278 */ 280 function head($url, $args = array() , $headers = null, $body = null) {279 function head($url, $args = array()) { 281 280 $defaults = array('method' => 'HEAD'); 282 281 $r = wp_parse_args( $args, $defaults ); 283 return $this->request($url, $r , $headers, $body);282 return $this->request($url, $r); 284 283 } 285 284 286 285 /** … … 299 298 } 300 299 301 300 /** 302 * Whether response code is in the 400 range.303 *304 * @access public305 * @static306 * @since 2.7307 *308 * @param array $response Array with code and message keys309 * @return bool True if 40x Response, false if something else.310 */311 function is400Response($response) {312 if ( (int) substr($response, 0, 1) == 4 )313 return true;314 return false;315 }316 317 /**318 * Whether the headers returned a redirect location.319 *320 * Actually just checks whether the location header exists.321 *322 * @access public323 * @static324 * @since 2.7325 *326 * @param array $headers Array with headers327 * @return bool True if Location header is found.328 */329 function isRedirect($headers) {330 if ( isset($headers['location']) )331 return true;332 return false;333 }334 335 /**336 301 * Transform header string into an array. 337 302 * 338 303 * If an array is given then it is assumed to be raw header data with … … 357 322 if ( empty($tempheader) ) 358 323 continue; 359 324 360 361 325 if ( false === strpos($tempheader, ':') ) { 362 326 list( , $iResponseCode, $strResponseMsg) = explode(' ', $tempheader, 3); 363 327 $response['code'] = $iResponseCode; … … 373 337 374 338 return array('response' => $response, 'headers' => $newheaders); 375 339 } 340 341 /** 342 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification. 343 * 344 * Based off the HTTP http_encoding_dechunk function. Does not support 345 * UTF-8. Does not support returning footer headers. Shouldn't be too 346 * difficult to support it though. 347 * 348 * @todo Add support for footer chunked headers. 349 * 350 * @static 351 * @param string $body Body content 352 * @return bool|string|WP_Error False if not chunked encoded. WP_Error on failure. Chunked decoded body on success. 353 */ 354 function chunkTransferDecode($body) { 355 $body = str_replace(array("\r\n", "\r"), "\n", $body); 356 // The body is not chunked encoding or is malformed. 357 if ( ! preg_match( '/^[0-9a-f]+(\s|\n)+/mi', trim($body) ) ) 358 return false; 359 360 $hex = ''; 361 $dec = 0; 362 $parsedBody = ''; 363 $parsedHeaders = array(); 364 365 $done = false; 366 367 do { 368 $hasChunk = (bool) preg_match( '/^([0-9a-f]+)(\s|\n)+/mi', $body, $match ); 369 370 if ( $hasChunk ) { 371 if ( empty($match[1]) ) { 372 return new WP_Error('http_chunked_decode', __('Does not appear to be chunked encoded or body is malformed.') ); 373 } 374 375 $length = hexdec( $match[1] ); 376 $chunkLength = strlen( $match[0] ); 377 378 if( $body{$length+$chunkLength} == "\n" ) 379 $length++; 380 381 $strBody = substr($body, strlen( $match[0] ), $length); 382 $parsedBody .= $strBody; 383 $body = str_replace(array($match[0], $strBody), '', $body); 384 385 if( "0" == $body ) { 386 $done = true; 387 return $parsedBody; // Ignore footer headers. 388 break; 389 } 390 } else { 391 return new WP_Error('http_chunked_decode', __('Does not appear to be chunked encoded or body is malformed.') ); 392 } 393 } while ( false === $done ); 394 } 376 395 } 377 396 378 397 /** … … 391 410 * 392 411 * Does not support non-blocking mode. 393 412 * 394 * @see WP_Http::re trieveFor default options descriptions.413 * @see WP_Http::request For default options descriptions. 395 414 * 396 415 * @since 2.7 397 416 * @access public 398 417 * @param string $url URI resource. 399 418 * @param str|array $args Optional. Override the defaults. 400 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.401 * @param string $body Optional. The body that should be sent. Expected to be already processed.402 419 * @return array 'headers', 'body', and 'response' keys. 403 420 */ 404 function request($url, $args = array() , $headers = null, $body = null) {421 function request($url, $args = array()) { 405 422 $defaults = array( 406 423 'method' => 'GET', 'timeout' => 3, 407 424 'redirection' => 5, 'httpversion' => '1.0', 408 'blocking' => true 425 'blocking' => true, 426 'headers' => array(), 'body' => null 409 427 ); 410 428 411 429 $r = wp_parse_args( $args, $defaults ); 412 430 431 if ( isset($r['headers']['User-Agent']) ) { 432 $r['user-agent'] = $r['headers']['User-Agent']; 433 unset($r['headers']['User-Agent']); 434 } else if( isset($r['headers']['user-agent']) ) { 435 $r['user-agent'] = $r['headers']['user-agent']; 436 unset($r['headers']['user-agent']); 437 } 438 413 439 $iError = null; // Store error number 414 440 $strError = null; // Store error string 415 441 … … 445 471 $strHeaders = ''; 446 472 $strHeaders .= strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n"; 447 473 $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n"; 448 if ( ! is_null($body) ) { 474 475 if( isset($r['user-agent']) ) 476 $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n"; 477 478 if ( ! is_null($r['body']) ) { 449 479 $strHeaders .= 'Content-Type: application/x-www-form-urlencoded; charset=' . get_option('blog_charset') . "\r\n"; 450 480 $strHeaders .= 'Content-Length: ' . strlen($body) . "\r\n"; 451 481 } 452 482 453 if ( is_array($ headers) ) {454 foreach ( (array) $ headersas $header => $headerValue )483 if ( is_array($r['headers']) ) { 484 foreach ( (array) $r['headers'] as $header => $headerValue ) 455 485 $strHeaders .= $header . ': ' . $headerValue . "\r\n"; 456 486 } else { 457 $strHeaders .= $ headers;487 $strHeaders .= $r['headers']; 458 488 } 459 489 460 490 $strHeaders .= "\r\n"; 461 491 462 if ( ! is_null($ body) )463 $strHeaders .= $ body;492 if ( ! is_null($r['body']) ) 493 $strHeaders .= $r['body']; 464 494 465 495 fwrite($handle, $strHeaders); 466 496 … … 481 511 $process = WP_Http::processResponse($strResponse); 482 512 $arrHeaders = WP_Http::processHeaders($process['headers']); 483 513 484 if ( WP_Http ::is400Response($arrHeaders['response']) )514 if ( WP_Http_Fsockopen::is400Response($arrHeaders['response']) ) 485 515 return new WP_Error('http_request_failed', $arrHeaders['response']['code'] . ': ' . $arrHeaders['response']['message']); 486 516 517 // If location is found, then assume redirect and redirect to location. 487 518 if ( isset($arrHeaders['headers']['location']) ) { 488 if ( $r['redirection']-- > 0 ) 489 return $this->request($arrHeaders['headers']['location'], $r, $headers, $body); 519 if ( $r['redirection']-- > 0 ) { 520 return $this->request($arrHeaders['headers']['location'], $r); 521 } 490 522 else 491 523 return new WP_Error('http_request_failed', __('Too many redirects.')); 492 524 } … … 507 539 508 540 return false; 509 541 } 542 543 /** 544 * Whether response code is in the 400 range. 545 * 546 * @access public 547 * @static 548 * @since 2.7 549 * 550 * @param array $response Array with code and message keys 551 * @return bool True if 40x Response, false if something else. 552 */ 553 function is400Response($response) { 554 if ( is_string($response) && (int) substr($response, 0, 1) == 4 ) 555 return true; 556 return false; 557 } 510 558 } 511 559 512 560 /** … … 536 584 * 537 585 * @param string $url URI resource. 538 586 * @param str|array $args Optional. Override the defaults. 539 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.540 * @param string $body Optional. The body that should be sent. Expected to be already processed.541 587 * @return array 'headers', 'body', and 'response' keys. 542 588 */ 543 function request($url, $args = array() , $headers = null, $body = null) {589 function request($url, $args = array()) { 544 590 global $http_response_header; 545 591 546 592 $defaults = array( 547 593 'method' => 'GET', 'timeout' => 3, 548 594 'redirection' => 5, 'httpversion' => '1.0', 549 'blocking' => true 595 'blocking' => true, 596 'headers' => array(), 'body' => null 550 597 ); 551 598 552 599 $r = wp_parse_args( $args, $defaults ); … … 591 638 592 639 $processedHeaders = WP_Http::processHeaders($theHeaders); 593 640 641 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] ) 642 $theBody = WP_Http::chunkTransferDecode($strResponse); 643 594 644 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']); 595 645 } 596 646 … … 629 679 * 630 680 * @param string $url 631 681 * @param str|array $args Optional. Override the defaults. 632 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.633 * @param string $body Optional. The body that should be sent. Expected to be already processed.634 682 * @return array 'headers', 'body', and 'response' keys. 635 683 */ 636 function request($url, $args = array() , $headers = null, $body = null) {684 function request($url, $args = array()) { 637 685 $defaults = array( 638 686 'method' => 'GET', 'timeout' => 3, 639 687 'redirection' => 5, 'httpversion' => '1.0', 640 'blocking' => true 688 'blocking' => true, 689 'headers' => array(), 'body' => null 641 690 ); 642 691 643 692 $r = wp_parse_args( $args, $defaults ); 644 693 645 if ( isset($headers['User-Agent']) ) { 646 $r['user-agent'] = $headers['User-Agent']; 647 unset($headers['User-Agent']); 648 } else if( isset($headers['user-agent']) ) { 649 $r['user-agent'] = $headers['user-agent']; 650 unset($headers['user-agent']); 651 } else { 652 $r['user-agent'] = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version ); 694 if ( isset($r['headers']['User-Agent']) ) { 695 $r['user-agent'] = $r['headers']['User-Agent']; 696 unset($r['headers']['User-Agent']); 697 } else if( isset($r['headers']['user-agent']) ) { 698 $r['user-agent'] = $r['headers']['user-agent']; 699 unset($r['headers']['user-agent']); 653 700 } 654 701 655 702 $arrURL = parse_url($url); … … 666 713 'user-agent' => $r['user-agent'], 667 714 'max_redirects' => $r['redirection'], 668 715 'protocol_version' => (float) $r['httpversion'], 669 'header' => $ headers,716 'header' => $r['headers'], 670 717 'timeout' => $r['timeout'] 671 718 ) 672 719 ); 673 720 674 if ( ! is_null($ body) )675 $arrContext['http']['content'] = $ body;721 if ( ! is_null($r['body']) ) 722 $arrContext['http']['content'] = $r['body']; 676 723 677 724 $context = stream_context_create($arrContext); 678 725 … … 695 742 $meta = stream_get_meta_data($handle); 696 743 $processedHeaders = WP_Http::processHeaders($meta['wrapper_data']); 697 744 745 if ( ! empty( $strResponse ) && isset( $processedHeaders['headers']['transfer-encoding'] ) && 'chunked' == $processedHeaders['headers']['transfer-encoding'] ) 746 $theBody = WP_Http::chunkTransferDecode($strResponse); 747 698 748 fclose($handle); 699 749 700 750 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response']); … … 743 793 * 744 794 * @param string $url 745 795 * @param str|array $args Optional. Override the defaults. 746 * @param array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.747 * @param string $body Optional. The body that should be sent. Expected to be already processed.748 796 * @return array 'headers', 'body', and 'response' keys. 749 797 */ 750 function request($url, $args = array(), $headers = null, $body = null) { 751 global $wp_version; 752 798 function request($url, $args = array()) { 753 799 $defaults = array( 754 800 'method' => 'GET', 'timeout' => 3, 755 801 'redirection' => 5, 'httpversion' => '1.0', 756 'blocking' => true 802 'blocking' => true, 803 'headers' => array(), 'body' => null 757 804 ); 758 805 759 806 $r = wp_parse_args( $args, $defaults ); 760 807 761 if ( isset($headers['User-Agent']) ) { 762 $r['user-agent'] = $headers['User-Agent']; 763 unset($headers['User-Agent']); 764 } else if( isset($headers['user-agent']) ) { 765 $r['user-agent'] = $headers['user-agent']; 766 unset($headers['user-agent']); 767 } else { 768 $r['user-agent'] = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version ); 808 if ( isset($r['headers']['User-Agent']) ) { 809 $r['user-agent'] = $r['headers']['User-Agent']; 810 unset($r['headers']['User-Agent']); 811 } else if( isset($r['headers']['user-agent']) ) { 812 $r['user-agent'] = $r['headers']['user-agent']; 813 unset($r['headers']['user-agent']); 769 814 } 770 815 771 816 switch ( $r['method'] ) { … … 792 837 'connecttimeout' => $r['timeout'], 793 838 'redirect' => $r['redirection'], 794 839 'useragent' => $r['user-agent'], 795 'headers' => $ headers,840 'headers' => $r['headers'], 796 841 ); 797 842 798 $strResponse = http_request($r['method'], $url, $ body, $options, $info);843 $strResponse = http_request($r['method'], $url, $r['body'], $options, $info); 799 844 800 845 if ( false === $strResponse ) 801 846 return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']); … … 850 895 * 851 896 * @param string $url 852 897 * @param str|array $args Optional. Override the defaults. 853 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs. Expects sanitized.854 * @param string $body Optional. The body that should be sent. Expected to be already processed.855 898 * @return array 'headers', 'body', and 'response' keys. 856 899 */ 857 function request($url, $args = array(), $headers = null, $body = null) { 858 global $wp_version; 859 900 function request($url, $args = array()) { 860 901 $defaults = array( 861 902 'method' => 'GET', 'timeout' => 3, 862 903 'redirection' => 5, 'httpversion' => '1.0', 863 'blocking' => true 904 'blocking' => true, 905 'headers' => array(), 'body' => null 864 906 ); 865 907 866 908 $r = wp_parse_args( $args, $defaults ); 867 909 868 if ( isset($headers['User-Agent']) ) { 869 $r['user-agent'] = $headers['User-Agent']; 870 unset($headers['User-Agent']); 871 } else if( isset($headers['user-agent']) ) { 872 $r['user-agent'] = $headers['user-agent']; 873 unset($headers['user-agent']); 874 } else { 875 $r['user-agent'] = apply_filters('http_headers_useragent', 'WordPress/' . $wp_version ); 910 if ( isset($r['headers']['User-Agent']) ) { 911 $r['user-agent'] = $r['headers']['User-Agent']; 912 unset($r['headers']['User-Agent']); 913 } else if( isset($r['headers']['user-agent']) ) { 914 $r['user-agent'] = $r['headers']['user-agent']; 915 unset($r['headers']['user-agent']); 876 916 } 877 917 878 918 $handle = curl_init(); … … 896 936 if ( !ini_get('safe_mode') && !ini_get('open_basedir') ) 897 937 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true ); 898 938 899 if( ! is_null($ headers) )900 curl_setopt( $handle, CURLOPT_HTTPHEADER, $ headers);939 if( ! is_null($r['headers']) ) 940 curl_setopt( $handle, CURLOPT_HTTPHEADER, $r['headers'] ); 901 941 902 942 if ( $r['httpversion'] == '1.0' ) 903 943 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); … … 915 955 list($theHeaders, $theBody) = explode("\r\n\r\n", $theResponse, 2); 916 956 $theHeaders = WP_Http::processHeaders($theHeaders); 917 957 958 if ( ! empty( $theBody ) && isset( $theHeaders['headers']['transfer-encoding'] ) && 'chunked' == $theHeaders['headers']['transfer-encoding'] ) 959 $theBody = WP_Http::chunkTransferDecode($theBody); 960 918 961 $response = array(); 919 962 $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE ); 920 963 $response['message'] = get_status_header_desc($response['code']); … … 986 1029 * 987 1030 * @param string $url Site URL to retrieve. 988 1031 * @param array $args Optional. Override the defaults. 989 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.990 * @param string $body Optional. The body that should be sent. Expected to be already processed.991 1032 * @return string The body of the response 992 1033 */ 993 function wp_remote_request($url, $args = array() , $headers = null, $body = null) {1034 function wp_remote_request($url, $args = array()) { 994 1035 $objFetchSite = _wp_http_get_object(); 995 996 return $objFetchSite->request($url, $args, $headers, $body); 1036 return $objFetchSite->request($url, $args); 997 1037 } 998 1038 999 1039 /** … … 1005 1045 * 1006 1046 * @param string $url Site URL to retrieve. 1007 1047 * @param array $args Optional. Override the defaults. 1008 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.1009 * @param string $body Optional. The body that should be sent. Expected to be already processed.1010 1048 * @return string The body of the response 1011 1049 */ 1012 function wp_remote_get($url, $args = array() , $headers = null, $body = null) {1050 function wp_remote_get($url, $args = array()) { 1013 1051 $objFetchSite = _wp_http_get_object(); 1014 1052 1015 return $objFetchSite->get($url, $args , $headers, $body);1053 return $objFetchSite->get($url, $args); 1016 1054 } 1017 1055 1018 1056 /** … … 1024 1062 * 1025 1063 * @param string $url Site URL to retrieve. 1026 1064 * @param array $args Optional. Override the defaults. 1027 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.1028 * @param string $body Optional. The body that should be sent. Expected to be already processed.1029 1065 * @return string The body of the response 1030 1066 */ 1031 function wp_remote_post($url, $args = array() , $headers = null, $body = null) {1067 function wp_remote_post($url, $args = array()) { 1032 1068 $objFetchSite = _wp_http_get_object(); 1033 1034 return $objFetchSite->post($url, $args, $headers, $body); 1069 return $objFetchSite->post($url, $args); 1035 1070 } 1036 1071 1037 1072 /** … … 1043 1078 * 1044 1079 * @param string $url Site URL to retrieve. 1045 1080 * @param array $args Optional. Override the defaults. 1046 * @param string|array $headers Optional. Either the header string or array of Header name and value pairs.1047 * @param string $body Optional. The body that should be sent. Expected to be already processed.1048 1081 * @return string The body of the response 1049 1082 */ 1050 function wp_remote_head($url, $args = array() , $headers = null, $body = null) {1083 function wp_remote_head($url, $args = array()) { 1051 1084 $objFetchSite = _wp_http_get_object(); 1052 1053 return $objFetchSite->head($url, $args, $headers, $body); 1085 return $objFetchSite->head($url, $args); 1054 1086 } 1055 1087 1056 1088 /** … … 1136 1168 return $response['body']; 1137 1169 } 1138 1170 1139 ?> 1171 ?> 1172 No newline at end of file