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