Ticket #9049: http-cookie-support.diff
File http-cookie-support.diff, 17.2 KB (added by , 15 years ago) |
---|
-
http.php
348 348 * 349 349 * @param string $url URI resource. 350 350 * @param str|array $args Optional. Override the defaults. 351 * @return boolean351 * @return array containing 'headers', 'body', 'response', 'cookies' 352 352 */ 353 353 function request( $url, $args = array() ) { 354 354 global $wp_version; … … 386 386 $r['user-agent'] = $r['headers']['user-agent']; 387 387 unset($r['headers']['user-agent']); 388 388 } 389 390 // Construct Cookie: header if any cookies are set 391 WP_Http::buildCookieHeader( $r ); 389 392 390 393 if( WP_Http_Encoding::is_available() ) 391 394 $r['headers']['Accept-Encoding'] = WP_Http_Encoding::accept_encoding(); … … 411 414 if( has_action('http_api_debug') ) 412 415 do_action('http_api_debug', $transports, 'transports_list'); 413 416 414 $response = array( 'headers' => array(), 'body' => '', 'response' => array('code' , 'message') );417 $response = array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 415 418 foreach( (array) $transports as $transport ) { 416 419 $response = $transport->request($url, $r); 417 420 418 421 if( has_action('http_api_debug') ) 419 422 do_action( 'http_api_debug', $response, 'response', get_class($transport) ); 420 423 … … 506 509 * @since 2.7.0 507 510 * 508 511 * @param string|array $headers 509 * @return array Processed string headers 512 * @return array Processed string headers. If duplicate headers are encountered, 513 * Then a numbered array is returned as the value of that header-key. 510 514 */ 511 515 function processHeaders($headers) { 512 516 if ( is_string($headers) ) … … 514 518 515 519 $response = array('code' => 0, 'message' => ''); 516 520 521 $cookies = array(); 517 522 $newheaders = array(); 518 523 foreach ( $headers as $tempheader ) { 519 524 if ( empty($tempheader) ) … … 528 533 529 534 list($key, $value) = explode(':', $tempheader, 2); 530 535 531 if ( ! empty($value) ) 532 $newheaders[strtolower($key)] = trim($value); 536 if ( !empty( $value ) ) { 537 $key = strtolower( $key ); 538 if ( isset( $newheaders[$key] ) ) { 539 $newheaders[$key] = array( $newheaders[$key], trim( $value ) ); 540 } else { 541 $newheaders[$key] = trim( $value ); 542 } 543 if ( 'set-cookie' == strtolower( $key ) ) 544 $cookies[] = new WP_Http_Cookie( $value ); 545 } 533 546 } 534 547 535 return array('response' => $response, 'headers' => $newheaders );548 return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies); 536 549 } 537 550 538 551 /** 552 * Takes the arguments for a ::request() and checks for the cookie array. 553 * If it's found, then it's assumed to contain WP_Http_Cookie objects, which 554 * are each parsed into strings and added to the Cookie: header (within the 555 * arguments array). Edits the array by reference. 556 * 557 * @access public 558 * @static 559 * 560 * @param array $r Full array of args passed into ::request() 561 */ 562 function buildCookieHeader( &$r ) { 563 if ( count( $r['cookies'] ) ) { 564 $cookies_header = ''; 565 foreach ( $r['cookies'] as $cookie ) { 566 $cookies_header .= $cookie->getHeaderValue() . '; '; 567 } 568 $cookies_header = substr( $cookies_header, 0, -2 ); 569 $r['headers']['cookie'] = $cookies_header; 570 } 571 } 572 573 /** 539 574 * Decodes chunk transfer-encoding, based off the HTTP 1.1 specification. 540 575 * 541 576 * Based off the HTTP http_encoding_dechunk function. Does not support … … 605 640 * @access public 606 641 * @param string $url URI resource. 607 642 * @param str|array $args Optional. Override the defaults. 608 * @return array 'headers', 'body', and 'response' keys.643 * @return array 'headers', 'body', 'cookies' and 'response' keys. 609 644 */ 610 645 function request($url, $args = array()) { 611 646 $defaults = array( … … 625 660 unset($r['headers']['user-agent']); 626 661 } 627 662 663 // Construct Cookie: header if any cookies are set 664 WP_Http::buildCookieHeader( $r ); 665 628 666 $iError = null; // Store error number 629 667 $strError = null; // Store error string 630 668 … … 697 735 698 736 if ( ! $r['blocking'] ) { 699 737 fclose($handle); 700 return array( 'headers' => array(), 'body' => '', 'response' => array('code' , 'message') );738 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 701 739 } 702 740 703 741 $strResponse = ''; … … 732 770 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders) ) 733 771 $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); 734 772 735 return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'] );773 return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies']); 736 774 } 737 775 738 776 /** … … 780 818 * 781 819 * @param string $url URI resource. 782 820 * @param str|array $args Optional. Override the defaults. 783 * @return array 'headers', 'body', and 'response' keys.821 * @return array 'headers', 'body', 'cookies' and 'response' keys. 784 822 */ 785 823 function request($url, $args = array()) { 786 824 global $http_response_header; … … 789 827 'method' => 'GET', 'timeout' => 5, 790 828 'redirection' => 5, 'httpversion' => '1.0', 791 829 'blocking' => true, 792 'headers' => array(), 'body' => null 830 'headers' => array(), 'body' => null, 'cookies' => array() 793 831 ); 794 832 795 833 $r = wp_parse_args( $args, $defaults ); 796 834 835 // This transport doesn't support cookies, throw an error if they are set 836 if ( count( $r['cookies'] ) ) 837 return new WP_Error( 'http_request_failed', __( 'WP_Http_Fopen() does not support sending cookies' ) ); 838 797 839 $arrURL = parse_url($url); 798 840 799 841 if ( false === $arrURL ) … … 816 858 817 859 if ( ! $r['blocking'] ) { 818 860 fclose($handle); 819 return array( 'headers' => array(), 'body' => '', 'response' => array('code' , 'message') );861 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 820 862 } 821 863 822 864 $strResponse = ''; … … 845 887 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders) ) 846 888 $strResponse = WP_Http_Encoding::decompress( $strResponse ); 847 889 848 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'] );890 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']); 849 891 } 850 892 851 893 /** … … 884 926 * 885 927 * @param string $url 886 928 * @param str|array $args Optional. Override the defaults. 887 * @return array 'headers', 'body', and 'response' keys.929 * @return array 'headers', 'body', 'cookies' and 'response' keys. 888 930 */ 889 931 function request($url, $args = array()) { 890 932 $defaults = array( 891 933 'method' => 'GET', 'timeout' => 5, 892 934 'redirection' => 5, 'httpversion' => '1.0', 893 935 'blocking' => true, 894 'headers' => array(), 'body' => null 936 'headers' => array(), 'body' => null, 'cookies' => array() 895 937 ); 896 938 897 939 $r = wp_parse_args( $args, $defaults ); … … 903 945 $r['user-agent'] = $r['headers']['user-agent']; 904 946 unset($r['headers']['user-agent']); 905 947 } 948 949 // Construct Cookie: header if any cookies are set 950 WP_Http::buildCookieHeader( $r ); 906 951 907 952 $arrURL = parse_url($url); 908 953 … … 951 996 if ( ! $r['blocking'] ) { 952 997 stream_set_blocking($handle, 0); 953 998 fclose($handle); 954 return array( 'headers' => array(), 'body' => '', 'response' => array('code' , 'message') );999 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 955 1000 } 956 1001 957 1002 $strResponse = stream_get_contents($handle); … … 971 1016 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($processedHeaders) ) 972 1017 $strResponse = WP_Http_Encoding::decompress( $strResponse ); 973 1018 974 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'] );1019 return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']); 975 1020 } 976 1021 977 1022 /** … … 1017 1062 * 1018 1063 * @param string $url 1019 1064 * @param str|array $args Optional. Override the defaults. 1020 * @return array 'headers', 'body', and 'response' keys.1065 * @return array 'headers', 'body', 'cookies' and 'response' keys. 1021 1066 */ 1022 1067 function request($url, $args = array()) { 1023 1068 $defaults = array( 1024 1069 'method' => 'GET', 'timeout' => 5, 1025 1070 'redirection' => 5, 'httpversion' => '1.0', 1026 1071 'blocking' => true, 1027 'headers' => array(), 'body' => null 1072 'headers' => array(), 'body' => null, 'cookies' => array() 1028 1073 ); 1029 1074 1030 1075 $r = wp_parse_args( $args, $defaults ); … … 1036 1081 $r['user-agent'] = $r['headers']['user-agent']; 1037 1082 unset($r['headers']['user-agent']); 1038 1083 } 1084 1085 // Construct Cookie: header if any cookies are set 1086 WP_Http::buildCookieHeader( $r ); 1039 1087 1040 1088 switch ( $r['method'] ) { 1041 1089 case 'POST': … … 1071 1119 return new WP_Error('http_request_failed', $info['response_code'] . ': ' . $info['error']); 1072 1120 1073 1121 if ( ! $r['blocking'] ) 1074 return array( 'headers' => array(), 'body' => '', 'response' => array('code' , 'message') );1122 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 1075 1123 1076 1124 list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2); 1077 1125 $theHeaders = WP_Http::processHeaders($theHeaders); … … 1090 1138 $theResponse['code'] = $info['response_code']; 1091 1139 $theResponse['message'] = get_status_header_desc($info['response_code']); 1092 1140 1093 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse );1141 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $theResponse, 'cookies' => $theHeaders['cookies']); 1094 1142 } 1095 1143 1096 1144 /** … … 1127 1175 * 1128 1176 * @param string $url 1129 1177 * @param str|array $args Optional. Override the defaults. 1130 * @return array 'headers', 'body', and 'response' keys.1178 * @return array 'headers', 'body', 'cookies' and 'response' keys. 1131 1179 */ 1132 1180 function request($url, $args = array()) { 1133 1181 $defaults = array( 1134 1182 'method' => 'GET', 'timeout' => 5, 1135 1183 'redirection' => 5, 'httpversion' => '1.0', 1136 1184 'blocking' => true, 1137 'headers' => array(), 'body' => null 1185 'headers' => array(), 'body' => null, 'cookies' => array() 1138 1186 ); 1139 1187 1140 1188 $r = wp_parse_args( $args, $defaults ); … … 1146 1194 $r['user-agent'] = $r['headers']['user-agent']; 1147 1195 unset($r['headers']['user-agent']); 1148 1196 } 1197 1198 // Construct Cookie: header if any cookies are set 1199 WP_Http::buildCookieHeader( $r ); 1149 1200 1150 1201 // cURL extension will sometimes fail when the timeout is less than 1 as 1151 1202 // it may round down to 0, which gives it unlimited timeout. … … 1154 1205 1155 1206 $handle = curl_init(); 1156 1207 curl_setopt( $handle, CURLOPT_URL, $url); 1157 1208 1158 1209 // The cURL extension requires that the option be set for the HEAD to 1159 1210 // work properly. 1160 1211 if ( 'HEAD' === $r['method'] ) { … … 1179 1230 if ( !ini_get('safe_mode') && !ini_get('open_basedir') ) 1180 1231 curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, true ); 1181 1232 1182 if( ! is_null($r['headers']) ) 1183 curl_setopt( $handle, CURLOPT_HTTPHEADER, $r['headers'] ); 1233 if ( !empty( $r['headers'] ) ) { 1234 // cURL expects full header strings in each element 1235 $headers = array(); 1236 foreach ( $r['headers'] as $name => $value ) { 1237 $headers[] = "{$name}: $value"; 1238 } 1239 curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers ); 1240 } 1184 1241 1185 1242 if ( $r['httpversion'] == '1.0' ) 1186 1243 curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 ); … … 1192 1249 // without some reference. 1193 1250 do_action_ref_array( 'http_api_curl', array(&$handle) ); 1194 1251 1195 // We don't need to return the body, so don't. Just execut ionrequest1252 // We don't need to return the body, so don't. Just execute request 1196 1253 // and return. 1197 1254 if ( ! $r['blocking'] ) { 1198 1255 curl_exec( $handle ); 1199 1256 curl_close( $handle ); 1200 return array( 'headers' => array(), 'body' => '', 'response' => array('code' , 'message') );1257 return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); 1201 1258 } 1202 1259 1203 1260 $theResponse = curl_exec( $handle ); … … 1217 1274 if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array(301, 302) ) ) 1218 1275 return new WP_Error('http_request_failed', __('Too many redirects.')); 1219 1276 1220 $theHeaders = array( 'headers' => array() );1277 $theHeaders = array( 'headers' => array(), 'cookies' => array() ); 1221 1278 $theBody = ''; 1222 1279 } 1223 1280 … … 1230 1287 if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders) ) 1231 1288 $theBody = WP_Http_Encoding::decompress( $theBody ); 1232 1289 1233 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response );1290 return array('headers' => $theHeaders['headers'], 'body' => $theBody, 'response' => $response, 'cookies' => $theHeaders['cookies']); 1234 1291 } 1235 1292 1236 1293 /** … … 1249 1306 } 1250 1307 } 1251 1308 1309 1252 1310 /** 1311 * Internal representation of a cookie. 1312 * 1313 * Returned cookies are represented using this class, and when cookies are 1314 * set, if they are not already a WP_Http_Cookie() object, then they are turned 1315 * into one. 1316 * 1317 * @package WordPress 1318 * @subpackage HTTP 1319 */ 1320 class WP_Http_Cookie { 1321 var $name, 1322 $value, 1323 $expires, 1324 $path, 1325 $domain; 1326 1327 /** 1328 * PHP4 style Constructor - Calls PHP5 Style Constructor 1329 */ 1330 function WP_Http_Cookie( $data ) { 1331 return $this->__construct( $data ); 1332 } 1333 1334 /** 1335 * Sets up this cookie object. 1336 * 1337 * @access public 1338 * 1339 * @param mixed $data Either an associative array describing the cookie, or a header-string detailing it. 1340 * If it's an array, it should include the following elements: 1341 * - name 1342 * - value [should NOT be urlencoded already] 1343 * - expires (optional) String or int (UNIX timestamp) 1344 * - path (optional) 1345 * - domain (optional) 1346 */ 1347 function __construct( $data ) { 1348 if ( is_string( $data ) ) { 1349 // Assume it's a header string direct from a previous request 1350 $pairs = explode( ';', $data ); 1351 1352 // Special handling for first pair; name=value. Also be careful of "=" in value 1353 $name = trim( substr( $pairs[0], 0, strpos( $pairs[0], '=' ) ) ); 1354 $value = substr( $pairs[0], strpos( $pairs[0], '=' ) + 1 ); 1355 $this->name = $name; 1356 $this->value = urldecode( $value ); 1357 array_shift( $pairs ); 1358 1359 // Set everything else as a property 1360 foreach ( $pairs as $pair ) { 1361 list( $key, $val ) = explode( '=', $pair ); 1362 $key = strtolower( trim( $key ) ); 1363 if ( 'expires' == $key ) 1364 $val = strtotime( $val ); 1365 $this->$key = $val; 1366 } 1367 } else { 1368 // Set properties based directly on parameters 1369 $this->name = $data['name']; 1370 $this->value = $data['value']; 1371 $this->expires = is_int( $data['expires'] ) ? $data['expires'] : strtotime( $data['expires'] ); 1372 $this->path = $data['path']; 1373 $this->domain = $data['domain']; 1374 } 1375 } 1376 1377 /** 1378 * Confirms that it's OK to send this cookie to the URL checked against. 1379 * 1380 * Decision is based on RFC 2109/2965, so look there for details on validity. 1381 * 1382 * @access public 1383 * 1384 * @param string $url URL you intend to send this cookie to 1385 * @return boolean TRUE if allowed, FALSE otherwise. 1386 */ 1387 function test( $url ) { 1388 // Expires - if expired then nothing else matters 1389 if ( time() > $this->expires ) 1390 return false; 1391 1392 // Get details on the URL we're thinking about sending to 1393 $url = parse_url( $url ); 1394 $url['port'] = isset( $url['port'] ) ? $url['port'] : 80; 1395 $url['path'] = isset( $url['path'] ) ? $url['path'] : '/'; 1396 1397 // Values to use for comparison against the URL 1398 $path = isset( $this->path ) ? $this->path : '/'; 1399 $port = isset( $this->port ) ? $this->port : 80; 1400 $domain = isset( $this->domain ) ? strtolower( $this->domain ) : strtolower( $url['host'] ); 1401 if ( false === stripos( $domain, '.' ) ) 1402 $domain .= '.local'; 1403 1404 // Host - very basic check that the request URL ends with the domain restriction (minus leading dot) 1405 $domain = substr( $domain, 0, 1 ) == '.' ? substr( $domain, 1 ) : $domain; 1406 if ( substr( $url['host'], -strlen( $domain ) ) != $domain ) 1407 return false; 1408 1409 // Port - supports "port-lists" in the format: "80,8000,8080" 1410 if ( !in_array( $url['port'], explode( ',', $port) ) ) 1411 return false; 1412 1413 // Path - request path must start with path restriction 1414 if ( substr( $url['path'], 0, strlen( $path ) ) != $path ) 1415 return false; 1416 1417 return true; 1418 } 1419 1420 function getHeaderValue() { 1421 if ( empty( $this->name ) || empty( $this->value ) ) 1422 return ''; 1423 1424 return $this->name . '=' . urlencode( $this->value ); 1425 } 1426 1427 function getFullHeader() { 1428 return 'Cookie: ' . $this->getHeaderValue(); 1429 } 1430 } 1431 1432 /** 1253 1433 * Returns the initialized WP_Http Object 1254 1434 * 1255 1435 * @since 2.7.0 … … 1272 1452 * The array structure is a little complex. 1273 1453 * 1274 1454 * <code> 1275 * $res = array( 'headers' => array(), 'response' => array('code' , 'message') );1455 * $res = array( 'headers' => array(), 'response' => array('code' => int, 'message' => string) ); 1276 1456 * </code> 1277 1457 * 1278 1458 * All of the headers in $res['headers'] are with the name as the key and the