[ Index ] |
PHP Cross Reference of YOURLS |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Functions that relate to HTTP requests 5 * 6 * On functions using the 3rd party library Requests: 7 * Their goal here is to provide convenient wrapper functions to the Requests library. There are 8 * 2 types of functions for each METHOD, where METHOD is 'get' or 'post' (implement more as needed) 9 * - yourls_http_METHOD() : 10 * Return a complete Response object (with ->body, ->headers, ->status_code, etc...) or 11 * a simple string (error message) 12 * - yourls_http_METHOD_body() : 13 * Return a string (response body) or null if there was an error 14 * 15 * @since 1.7 16 */ 17 18 use WpOrg\Requests\Requests; 19 20 /** 21 * Perform a GET request, return response object or error string message 22 * 23 * Notable object properties: body, headers, status_code 24 * 25 * @since 1.7 26 * @see yourls_http_request 27 * @param string $url URL to request 28 * @param array $headers HTTP headers to send 29 * @param array $data GET data 30 * @param array $options Options to pass to Requests 31 * @return mixed Response object, or error string 32 */ 33 function yourls_http_get( $url, $headers = array(), $data = array(), $options = array() ) { 34 return yourls_http_request( 'GET', $url, $headers, $data, $options ); 35 } 36 37 /** 38 * Perform a GET request, return body or null if there was an error 39 * 40 * @since 1.7 41 * @see yourls_http_request 42 * @param string $url URL to request 43 * @param array $headers HTTP headers to send 44 * @param array $data GET data 45 * @param array $options Options to pass to Requests 46 * @return mixed String (page body) or null if error 47 */ 48 function yourls_http_get_body( $url, $headers = array(), $data = array(), $options = array() ) { 49 $return = yourls_http_get( $url, $headers, $data, $options ); 50 return isset( $return->body ) ? $return->body : null; 51 } 52 53 /** 54 * Perform a POST request, return response object 55 * 56 * Notable object properties: body, headers, status_code 57 * 58 * @since 1.7 59 * @see yourls_http_request 60 * @param string $url URL to request 61 * @param array $headers HTTP headers to send 62 * @param array $data POST data 63 * @param array $options Options to pass to Requests 64 * @return mixed Response object, or error string 65 */ 66 function yourls_http_post( $url, $headers = array(), $data = array(), $options = array() ) { 67 return yourls_http_request( 'POST', $url, $headers, $data, $options ); 68 } 69 70 /** 71 * Perform a POST request, return body 72 * 73 * Wrapper for yourls_http_request() 74 * 75 * @since 1.7 76 * @see yourls_http_request 77 * @param string $url URL to request 78 * @param array $headers HTTP headers to send 79 * @param array $data POST data 80 * @param array $options Options to pass to Requests 81 * @return mixed String (page body) or null if error 82 */ 83 function yourls_http_post_body( $url, $headers = array(), $data = array(), $options = array() ) { 84 $return = yourls_http_post( $url, $headers, $data, $options ); 85 return isset( $return->body ) ? $return->body : null; 86 } 87 88 /** 89 * Get proxy information 90 * 91 * @uses YOURLS_PROXY YOURLS_PROXY_USERNAME YOURLS_PROXY_PASSWORD 92 * @since 1.7.1 93 * @return mixed false if no proxy is defined, or string like '10.0.0.201:3128' or array like ('10.0.0.201:3128', 'username', 'password') 94 */ 95 function yourls_http_get_proxy() { 96 $proxy = false; 97 98 if( defined( 'YOURLS_PROXY' ) ) { 99 $proxy = YOURLS_PROXY; 100 if( defined( 'YOURLS_PROXY_USERNAME' ) && defined( 'YOURLS_PROXY_PASSWORD' ) ) { 101 $proxy = array( YOURLS_PROXY, YOURLS_PROXY_USERNAME, YOURLS_PROXY_PASSWORD ); 102 } 103 } 104 105 return yourls_apply_filter( 'http_get_proxy', $proxy ); 106 } 107 108 /** 109 * Get list of hosts that should bypass the proxy 110 * 111 * @uses YOURLS_PROXY_BYPASS_HOSTS 112 * @since 1.7.1 113 * @return mixed false if no host defined, or string like "example.com, *.mycorp.com" 114 */ 115 function yourls_http_get_proxy_bypass_host() { 116 $hosts = defined( 'YOURLS_PROXY_BYPASS_HOSTS' ) ? YOURLS_PROXY_BYPASS_HOSTS : false; 117 118 return yourls_apply_filter( 'http_get_proxy_bypass_host', $hosts ); 119 } 120 121 /** 122 * Default HTTP requests options for YOURLS 123 * 124 * For a list of all available options, see function request() in /includes/Requests/Requests.php 125 * 126 * @since 1.7 127 * @return array Options 128 */ 129 function yourls_http_default_options() { 130 $options = array( 131 'timeout' => yourls_apply_filter( 'http_default_options_timeout', 3 ), 132 'useragent' => yourls_http_user_agent(), 133 'follow_redirects' => true, 134 'redirects' => 3, 135 ); 136 137 if( yourls_http_get_proxy() ) { 138 $options['proxy'] = yourls_http_get_proxy(); 139 } 140 141 return yourls_apply_filter( 'http_default_options', $options ); 142 } 143 144 /** 145 * Whether URL should be sent through the proxy server. 146 * 147 * Concept stolen from WordPress. The idea is to allow some URLs, including localhost and the YOURLS install itself, 148 * to be requested directly and bypassing any defined proxy. 149 * 150 * @uses YOURLS_PROXY 151 * @uses YOURLS_PROXY_BYPASS_HOSTS 152 * @since 1.7 153 * @param string $url URL to check 154 * @return bool true to request through proxy, false to request directly 155 */ 156 function yourls_send_through_proxy( $url ) { 157 158 // Allow plugins to short-circuit the whole function 159 $pre = yourls_apply_filter( 'shunt_send_through_proxy', null, $url ); 160 if ( null !== $pre ) 161 return $pre; 162 163 $check = @parse_url( $url ); 164 165 if( !isset( $check['host'] ) ) { 166 return false; 167 } 168 169 // Malformed URL, can not process, but this could mean ssl, so let through anyway. 170 if ( $check === false ) 171 return true; 172 173 // Self and loopback URLs are considered local (':' is parse_url() host on '::1') 174 $home = parse_url( yourls_get_yourls_site() ); 175 $local = array( 'localhost', '127.0.0.1', '127.1', '[::1]', ':', $home['host'] ); 176 177 if( in_array( $check['host'], $local ) ) 178 return false; 179 180 $bypass = yourls_http_get_proxy_bypass_host(); 181 182 if( $bypass === false OR $bypass === '' ) { 183 return true; 184 } 185 186 // Build array of hosts to bypass 187 static $bypass_hosts; 188 static $wildcard_regex = false; 189 if ( null == $bypass_hosts ) { 190 $bypass_hosts = preg_split( '|\s*,\s*|', $bypass ); 191 192 if ( false !== strpos( $bypass, '*' ) ) { 193 $wildcard_regex = array(); 194 foreach ( $bypass_hosts as $host ) { 195 $wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) ); 196 if ( false !== strpos( $host, '*' ) ) { 197 $wildcard_regex[] = str_replace( '\*\.', '', preg_quote( $host, '/' ) ); 198 } 199 } 200 $wildcard_regex = '/^(' . implode( '|', $wildcard_regex ) . ')$/i'; 201 } 202 } 203 204 if ( !empty( $wildcard_regex ) ) 205 return !preg_match( $wildcard_regex, $check['host'] ); 206 else 207 return !in_array( $check['host'], $bypass_hosts ); 208 } 209 210 /** 211 * Perform a HTTP request, return response object 212 * 213 * @since 1.7 214 * @param string $type HTTP request type (GET, POST) 215 * @param string $url URL to request 216 * @param array $headers Extra headers to send with the request 217 * @param array $data Data to send either as a query string for GET requests, or in the body for POST requests 218 * @param array $options Options for the request (see /includes/Requests/Requests.php:request()) 219 * @return object WpOrg\Requests\Response object 220 */ 221 function yourls_http_request( $type, $url, $headers, $data, $options ) { 222 223 // Allow plugins to short-circuit the whole function 224 $pre = yourls_apply_filter( 'shunt_yourls_http_request', null, $type, $url, $headers, $data, $options ); 225 if ( null !== $pre ) 226 return $pre; 227 228 $options = array_merge( yourls_http_default_options(), $options ); 229 230 if( yourls_http_get_proxy() && !yourls_send_through_proxy( $url ) ) { 231 unset( $options['proxy'] ); 232 } 233 234 // filter everything 235 $type = yourls_apply_filter('http_request_type', $type); 236 $url = yourls_apply_filter('http_request_url', $url); 237 $headers = yourls_apply_filter('http_request_headers', $headers); 238 $data = yourls_apply_filter('http_request_data', $data); 239 $options = yourls_apply_filter('http_request_options', $options); 240 241 try { 242 $result = Requests::request( $url, $headers, $data, $type, $options ); 243 } catch( \WpOrg\Requests\Exception $e ) { 244 $result = yourls_debug_log( $e->getMessage() . ' (' . $type . ' on ' . $url . ')' ); 245 }; 246 247 return $result; 248 } 249 250 /** 251 * Return funky user agent string 252 * 253 * @since 1.5 254 * @return string UA string 255 */ 256 function yourls_http_user_agent() { 257 return yourls_apply_filter( 'http_user_agent', 'YOURLS v'.YOURLS_VERSION.' +http://yourls.org/ (running on '.yourls_get_yourls_site().')' ); 258 } 259 260 /** 261 * Check api.yourls.org if there's a newer version of YOURLS 262 * 263 * This function collects various stats to help us improve YOURLS. See the blog post about it: 264 * http://blog.yourls.org/2014/01/on-yourls-1-7-and-api-yourls-org/ 265 * Results of requests sent to api.yourls.org are stored in option 'core_version_checks' and is an object 266 * with the following properties: 267 * - failed_attempts : number of consecutive failed attempts 268 * - last_attempt : time() of last attempt 269 * - last_result : content retrieved from api.yourls.org during previous check 270 * - version_checked : installed YOURLS version that was last checked 271 * 272 * @since 1.7 273 * @return mixed JSON data if api.yourls.org successfully requested, false otherwise 274 */ 275 function yourls_check_core_version() { 276 277 global $yourls_user_passwords; 278 279 $checks = yourls_get_option( 'core_version_checks' ); 280 281 // Invalidate check data when YOURLS version changes 282 if ( is_object( $checks ) && YOURLS_VERSION != $checks->version_checked ) { 283 $checks = false; 284 } 285 286 if( !is_object( $checks ) ) { 287 $checks = new stdClass; 288 $checks->failed_attempts = 0; 289 $checks->last_attempt = 0; 290 $checks->last_result = ''; 291 $checks->version_checked = YOURLS_VERSION; 292 } 293 294 // Total number of links and clicks 295 list( $total_urls, $total_clicks ) = array_values(yourls_get_db_stats()); 296 297 // The collection of stuff to report 298 $stuff = array( 299 // Globally uniquish site identifier 300 // This uses const YOURLS_SITE and not yourls_get_yourls_site() to prevent creating another id for an already known install 301 'md5' => md5( YOURLS_SITE . YOURLS_ABSPATH ), 302 303 // Install information 304 'failed_attempts' => $checks->failed_attempts, 305 'yourls_site' => defined( 'YOURLS_SITE' ) ? yourls_get_yourls_site() : 'unknown', 306 'yourls_version' => defined( 'YOURLS_VERSION' ) ? YOURLS_VERSION : 'unknown', 307 'php_version' => PHP_VERSION, 308 'mysql_version' => yourls_get_db()->mysql_version(), 309 'locale' => yourls_get_locale(), 310 311 // custom DB driver if any, and useful common PHP extensions 312 'db_driver' => defined( 'YOURLS_DB_DRIVER' ) ? YOURLS_DB_DRIVER : 'unset', 313 'db_ext_pdo' => extension_loaded( 'PDO' ) ? 1 : 0, 314 'db_ext_mysql' => extension_loaded( 'mysql' ) ? 1 : 0, 315 'db_ext_mysqli' => extension_loaded( 'mysqli' ) ? 1 : 0, 316 'ext_curl' => extension_loaded( 'curl' ) ? 1 : 0, 317 318 // Config information 319 'yourls_private' => defined( 'YOURLS_PRIVATE' ) && YOURLS_PRIVATE ? 1 : 0, 320 'yourls_unique' => defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS ? 1 : 0, 321 'yourls_url_convert' => defined( 'YOURLS_URL_CONVERT' ) ? YOURLS_URL_CONVERT : 'unknown', 322 323 // Usage information 324 'num_users' => count( $yourls_user_passwords ), 325 'num_active_plugins' => yourls_has_active_plugins(), 326 'num_pages' => defined( 'YOURLS_PAGEDIR' ) ? count( (array) glob( YOURLS_PAGEDIR .'/*.php') ) : 0, 327 'num_links' => $total_urls, 328 'num_clicks' => $total_clicks, 329 ); 330 331 $stuff = yourls_apply_filter( 'version_check_stuff', $stuff ); 332 333 // Send it in 334 $url = 'http://api.yourls.org/core/version/1.1/'; 335 if( yourls_can_http_over_ssl() ) { 336 $url = yourls_set_url_scheme($url, 'https'); 337 } 338 $req = yourls_http_post( $url, array(), $stuff ); 339 340 $checks->last_attempt = time(); 341 $checks->version_checked = YOURLS_VERSION; 342 343 // Unexpected results ? 344 if( is_string( $req ) or !$req->success ) { 345 $checks->failed_attempts = $checks->failed_attempts + 1; 346 yourls_update_option( 'core_version_checks', $checks ); 347 if( is_string($req) ) { 348 yourls_debug_log('Version check failed: ' . $req); 349 } 350 return false; 351 } 352 353 // Parse response 354 $json = json_decode( trim( $req->body ) ); 355 356 if( yourls_validate_core_version_response($json) ) { 357 // All went OK - mark this down 358 $checks->failed_attempts = 0; 359 $checks->last_result = $json; 360 yourls_update_option( 'core_version_checks', $checks ); 361 362 return $json; 363 } 364 365 // Request returned actual result, but not what we expected 366 return false; 367 } 368 369 /** 370 * Make sure response from api.yourls.org is valid 371 * 372 * 1) we should get a json object with two following properties: 373 * 'latest' => a string representing a YOURLS version number, eg '1.2.3' 374 * 'zipurl' => a string for a zip package URL, from github, eg 'https://api.github.com/repos/YOURLS/YOURLS/zipball/1.2.3' 375 * 2) 'latest' and version extracted from 'zipurl' should match 376 * 3) the object should not contain any other key 377 * 378 * @since 1.7.7 379 * @param object $json JSON object to check 380 * @return bool true if seems legit, false otherwise 381 */ 382 function yourls_validate_core_version_response($json) { 383 return ( 384 yourls_validate_core_version_response_keys($json) 385 && $json->latest === yourls_sanitize_version($json->latest) 386 && $json->zipurl === yourls_sanitize_url($json->zipurl) 387 && $json->latest === yourls_get_version_from_zipball_url($json->zipurl) 388 && yourls_is_valid_github_repo_url($json->zipurl) 389 ); 390 } 391 392 /** 393 * Get version number from Github zipball URL (last part of URL, really) 394 * 395 * @since 1.8.3 396 * @param string $zipurl eg 'https://api.github.com/repos/YOURLS/YOURLS/zipball/1.2.3' 397 * @return string 398 */ 399 function yourls_get_version_from_zipball_url($zipurl) { 400 $version = ''; 401 $parts = explode('/', parse_url(yourls_sanitize_url($zipurl), PHP_URL_PATH) ?? ''); 402 // expect at least 1 slash in path, return last part 403 if( count($parts) > 1 ) { 404 $version = end($parts); 405 } 406 return $version; 407 } 408 409 /** 410 * Check if URL is from YOURLS/YOURLS repo on github 411 * 412 * @since 1.8.3 413 * @param string $url URL to check 414 * @return bool 415 */ 416 function yourls_is_valid_github_repo_url($url) { 417 $url = yourls_sanitize_url($url); 418 return ( 419 join('.',array_slice(explode('.', parse_url($url, PHP_URL_HOST) ?? ''), -2, 2)) === 'github.com' 420 // explodes on '.' (['api','github','com']) and keeps the last two elements 421 // to make sure domain is either github.com or one of its subdomain (api.github.com for instance) 422 // TODO: keep an eye on Github API to make sure it doesn't change some day to another domain (githubapi.com, ...) 423 && substr( parse_url($url, PHP_URL_PATH), 0, 21 ) === '/repos/YOURLS/YOURLS/' 424 // make sure path starts with '/repos/YOURLS/YOURLS/' 425 ); 426 } 427 428 /** 429 * Check if object has only expected keys 'latest' and 'zipurl' containing strings 430 * 431 * @since 1.8.3 432 * @param object $json 433 * @return bool 434 */ 435 function yourls_validate_core_version_response_keys($json) { 436 $keys = array('latest', 'zipurl'); 437 return ( 438 count(array_diff(array_keys((array)$json), $keys)) === 0 439 && isset($json->latest) 440 && isset($json->zipurl) 441 && is_string($json->latest) 442 && is_string($json->zipurl) 443 ); 444 } 445 446 /** 447 * Determine if we want to check for a newer YOURLS version (and check if applicable) 448 * 449 * Currently checks are performed every 24h and only when someone is visiting an admin page. 450 * In the future (1.8?) maybe check with cronjob emulation instead. 451 * 452 * @since 1.7 453 * @return bool true if a check was needed and successfully performed, false otherwise 454 */ 455 function yourls_maybe_check_core_version() { 456 // Allow plugins to short-circuit the whole function 457 $pre = yourls_apply_filter('shunt_maybe_check_core_version', null); 458 if (null !== $pre) { 459 return $pre; 460 } 461 462 if (yourls_skip_version_check()) { 463 return false; 464 } 465 466 if (!yourls_is_admin()) { 467 return false; 468 } 469 470 $checks = yourls_get_option( 'core_version_checks' ); 471 472 /* We don't want to check if : 473 - last_result is set (a previous check was performed) 474 - and it was less than 24h ago (or less than 2h ago if it wasn't successful) 475 - and version checked matched version running 476 Otherwise, we want to check. 477 */ 478 if( !empty( $checks->last_result ) 479 AND 480 ( 481 ( $checks->failed_attempts == 0 && ( ( time() - $checks->last_attempt ) < 24 * 3600 ) ) 482 OR 483 ( $checks->failed_attempts > 0 && ( ( time() - $checks->last_attempt ) < 2 * 3600 ) ) 484 ) 485 AND ( $checks->version_checked == YOURLS_VERSION ) 486 ) 487 return false; 488 489 // We want to check if there's a new version 490 $new_check = yourls_check_core_version(); 491 492 // Could not check for a new version, and we don't have ancient data 493 if( false == $new_check && !isset( $checks->last_result->latest ) ) 494 return false; 495 496 return true; 497 } 498 499 /** 500 * Check if user setting for skipping version check is set 501 * 502 * @since 1.8.2 503 * @return bool 504 */ 505 function yourls_skip_version_check() { 506 return yourls_apply_filter('skip_version_check', defined('YOURLS_NO_VERSION_CHECK') && YOURLS_NO_VERSION_CHECK); 507 } 508 509 /** 510 * Check if server can perform HTTPS requests, return bool 511 * 512 * @since 1.7.1 513 * @return bool whether the server can perform HTTP requests over SSL 514 */ 515 function yourls_can_http_over_ssl() { 516 $ssl_curl = $ssl_socket = false; 517 518 if( function_exists( 'curl_exec' ) ) { 519 $curl_version = curl_version(); 520 $ssl_curl = ( $curl_version['features'] & CURL_VERSION_SSL ); 521 } 522 523 if( function_exists( 'stream_socket_client' ) ) { 524 $ssl_socket = extension_loaded( 'openssl' ) && function_exists( 'openssl_x509_parse' ); 525 } 526 527 return ( $ssl_curl OR $ssl_socket ); 528 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Jan 21 05:10:11 2025 | Cross-referenced by PHPXref 0.7.1 |