| @@ -35,24 +35,9 @@ | ||
| 35 | 35 | |
| 36 | 36 | public const STATE_KEY = 'xspeed_preloader_state'; |
| 37 | 37 | public const STATE_TTL = 86400; // 24h — long enough for slow crawls. |
| 38 | 38 | public const CRON_HOOK = 'xspeed_preloader_tick'; |
| 39 | - /** | |
| 40 | - * User-agent for every request the preloader makes. | |
| 41 | - * | |
| 42 | - * Deliberately contains no substring from the 7G/8G bad-bot lists. The | |
| 43 | - * previous value, "xSpeed-Preloader/1.0", matched the `loader` token in | |
| 44 | - * the alphabetical slice `(linkscan|linkwalker|loader|lwp-download|...)` | |
| 45 | - * — a match on "Pre*loader*" — so nginx ports of 8G answered every warm | |
| 46 | - * with 403 and newly published posts were never warmed. Upstream 8G | |
| 47 | - * v1.5 has since dropped `loader`, but forks and vendored copies (xCloud | |
| 48 | - * among them) still ship the older slice, so the name has to stay clear | |
| 49 | - * of it. "Warmer" matches nothing in either list. (#481) | |
| 50 | - * | |
| 51 | - * Read through user_agent() rather than using this constant directly, so | |
| 52 | - * the `xspeed_preloader_user_agent` filter applies. | |
| 53 | - */ | |
| 54 | - public const USER_AGENT = 'xSpeed-Warmer/1.0 (+cache warmer; admin-initiated)'; | |
| 39 | + public const USER_AGENT = 'xSpeed-Preloader/1.0 (+cache warmer; admin-initiated)'; | |
| 55 | 40 | public const REQUEST_TIMEOUT = 8; |
| 56 | 41 | |
| 57 | 42 | /** |
| 58 | 43 | * Default cap on NEW remote images resolved per warmed page. |
| @@ -72,102 +57,8 @@ | ||
| 72 | 57 | */ |
| 73 | 58 | private const REMOTE_DIMENSION_LIMIT = 20; |
| 74 | 59 | |
| 75 | 60 | /** |
| 76 | - * The user-agent every preloader request sends. | |
| 77 | - * | |
| 78 | - * Filterable because the blocking rule lives on the server, not here: a | |
| 79 | - * host with its own bad-bot list can clear a warm without patching the | |
| 80 | - * plugin or waiting for a release. An empty filter return is ignored — | |
| 81 | - * sending no UA gets a request blocked at least as often. (#481) | |
| 82 | - */ | |
| 83 | - public static function user_agent(): string { | |
| 84 | - /** | |
| 85 | - * Filter the preloader's user-agent string. | |
| 86 | - * | |
| 87 | - * @param string $user_agent Default self::USER_AGENT. | |
| 88 | - */ | |
| 89 | - $ua = apply_filters( 'xspeed_preloader_user_agent', self::USER_AGENT ); | |
| 90 | - | |
| 91 | - return ( is_string( $ua ) && '' !== trim( $ua ) ) ? trim( $ua ) : self::USER_AGENT; | |
| 92 | - } | |
| 93 | - | |
| 94 | - /** | |
| 95 | - * Is this status code the signature of a firewall refusing our warmer? | |
| 96 | - * | |
| 97 | - * 403 and 406 are what bad-bot rules (7G/8G, mod_security, Wordfence) | |
| 98 | - * answer with. We only ever warm our OWN origin, and a page a visitor can | |
| 99 | - * load must be loadable by us too — so these codes mean the request was | |
| 100 | - * judged by its user-agent, not that the page is missing or broken. (#481) | |
| 101 | - */ | |
| 102 | - private static function is_firewall_block( int $code ): bool { | |
| 103 | - return in_array( $code, array( 403, 406 ), true ); | |
| 104 | - } | |
| 105 | - | |
| 106 | - /** | |
| 107 | - * Explain a warm failure in terms the admin can act on. | |
| 108 | - * | |
| 109 | - * A bare "HTTP 403" sent people hunting a broken page; the page is fine, | |
| 110 | - * and the fix is a server rule, so the message has to name the cause and | |
| 111 | - * the exact UA to allow. (#481) | |
| 112 | - */ | |
| 113 | - private static function failure_detail( int $code ): string { | |
| 114 | - if ( ! self::is_firewall_block( $code ) ) { | |
| 115 | - return sprintf( 'HTTP %d', $code ); | |
| 116 | - } | |
| 117 | - | |
| 118 | - return sprintf( | |
| 119 | - 'HTTP %d — your server\'s firewall is blocking the xSpeed cache warmer by user-agent, so this page was not warmed. Allow the user-agent "%s" (on xCloud this is the 8G firewall\'s bad-bot rule), or change it with the xspeed_preloader_user_agent filter.', | |
| 120 | - $code, | |
| 121 | - self::user_agent() | |
| 122 | - ); | |
| 123 | - } | |
| 124 | - | |
| 125 | - /** Option holding the last firewall-shaped warm refusal. */ | |
| 126 | - public const FIREWALL_BLOCK_OPTION = 'xspeed_preloader_firewall_block'; | |
| 127 | - | |
| 128 | - /** | |
| 129 | - * Record that the origin refused a warm by user-agent, for ui_notices(). | |
| 130 | - * | |
| 131 | - * An option rather than a transient: the condition is a server rule that | |
| 132 | - * persists until someone changes it, and a notice that expired on its own | |
| 133 | - * would let a site go back to never warming, silently. Cleared by | |
| 134 | - * clear_firewall_block() on the first warm that succeeds. (#481) | |
| 135 | - */ | |
| 136 | - private static function remember_firewall_block( string $url, int $code ): void { | |
| 137 | - if ( ! function_exists( 'update_option' ) ) { | |
| 138 | - return; | |
| 139 | - } | |
| 140 | - update_option( | |
| 141 | - self::FIREWALL_BLOCK_OPTION, | |
| 142 | - array( | |
| 143 | - 'url' => $url, | |
| 144 | - 'code' => $code, | |
| 145 | - 'user_agent' => self::user_agent(), | |
| 146 | - 'ts' => time(), | |
| 147 | - ), | |
| 148 | - false | |
| 149 | - ); | |
| 150 | - } | |
| 151 | - | |
| 152 | - /** Forget the firewall block once a warm gets through. */ | |
| 153 | - public static function clear_firewall_block(): void { | |
| 154 | - if ( function_exists( 'delete_option' ) && self::firewall_block() ) { | |
| 155 | - delete_option( self::FIREWALL_BLOCK_OPTION ); | |
| 156 | - } | |
| 157 | - } | |
| 158 | - | |
| 159 | - /** The last firewall-shaped refusal, or null when there isn't one. */ | |
| 160 | - public static function firewall_block(): ?array { | |
| 161 | - if ( ! function_exists( 'get_option' ) ) { | |
| 162 | - return null; | |
| 163 | - } | |
| 164 | - $block = get_option( self::FIREWALL_BLOCK_OPTION, null ); | |
| 165 | - | |
| 166 | - return ( is_array( $block ) && ! empty( $block['code'] ) ) ? $block : null; | |
| 167 | - } | |
| 168 | - | |
| 169 | - /** | |
| 170 | 61 | * How many new remote images one warmed page may resolve. |
| 171 | 62 | */ |
| 172 | 63 | private static function remote_dimension_limit(): int { |
| 173 | 64 | /** |
| @@ -389,9 +280,9 @@ | ||
| 389 | 280 | $url, |
| 390 | 281 | array( |
| 391 | 282 | 'timeout' => self::REQUEST_TIMEOUT, |
| 392 | 283 | 'sslverify' => false, |
| 393 | - 'user-agent' => self::user_agent(), | |
| 284 | + 'user-agent' => self::USER_AGENT, | |
| 394 | 285 | 'blocking' => true, |
| 395 | 286 | ) |
| 396 | 287 | ); |
| 397 | 288 | if ( is_wp_error( $response ) ) { |
| @@ -405,19 +296,13 @@ | ||
| 405 | 296 | $code = (int) wp_remote_retrieve_response_code( $response ); |
| 406 | 297 | if ( $code >= 400 ) { |
| 407 | 298 | Activity_Log::record( |
| 408 | 299 | 'preloader_warm_failed', |
| 409 | - sprintf( 'Warm %s failed (%s): %s', $cause, $url, self::failure_detail( $code ) ), | |
| 300 | + sprintf( 'Warm %s failed (%s): HTTP %d', $cause, $url, $code ), | |
| 410 | 301 | Activity_Log::WARN |
| 411 | 302 | ); |
| 412 | - if ( self::is_firewall_block( $code ) ) { | |
| 413 | - self::remember_firewall_block( $url, $code ); | |
| 414 | - } | |
| 415 | 303 | return false; |
| 416 | 304 | } |
| 417 | - // A warm that got through proves the firewall is no longer refusing us, | |
| 418 | - // so the notice must go — otherwise it outlives the problem. (#481) | |
| 419 | - self::clear_firewall_block(); | |
| 420 | 305 | Activity_Log::record( |
| 421 | 306 | 'preloader_warmed_one', |
| 422 | 307 | sprintf( 'Warmed %s (%s)', $url, $cause ), |
| 423 | 308 | Activity_Log::INFO |
| @@ -430,9 +315,9 @@ | ||
| 430 | 315 | $url, |
| 431 | 316 | array( |
| 432 | 317 | 'timeout' => self::REQUEST_TIMEOUT, |
| 433 | 318 | 'sslverify' => false, |
| 434 | - 'user-agent' => self::user_agent(), | |
| 319 | + 'user-agent' => self::USER_AGENT, | |
| 435 | 320 | 'headers' => array( |
| 436 | 321 | 'Accept' => 'text/html,application/xhtml+xml', |
| 437 | 322 | ), |
| 438 | 323 | 'blocking' => true, |
| @@ -452,19 +337,15 @@ | ||
| 452 | 337 | $code = (int) wp_remote_retrieve_response_code( $response ); |
| 453 | 338 | if ( $code >= 400 ) { |
| 454 | 339 | $state['errors'][] = array( |
| 455 | 340 | 'url' => $url, |
| 456 | - 'error' => self::failure_detail( $code ), | |
| 341 | + 'error' => sprintf( 'HTTP %d', $code ), | |
| 457 | 342 | 'ts' => time(), |
| 458 | 343 | ); |
| 459 | 344 | $state['errors'] = array_slice( $state['errors'], -20 ); |
| 460 | - if ( self::is_firewall_block( $code ) ) { | |
| 461 | - self::remember_firewall_block( $url, $code ); | |
| 462 | - } | |
| 463 | 345 | return; |
| 464 | 346 | } |
| 465 | 347 | |
| 466 | - self::clear_firewall_block(); | |
| 467 | 348 | self::warm_remote_dimensions( (string) wp_remote_retrieve_body( $response ) ); |
| 468 | 349 | } |
| 469 | 350 | |
| 470 | 351 | /** |
| @@ -725,9 +606,9 @@ | ||
| 725 | 606 | $sitemap_url, |
| 726 | 607 | array( |
| 727 | 608 | 'timeout' => self::REQUEST_TIMEOUT, |
| 728 | 609 | 'sslverify' => false, |
| 729 | - 'user-agent' => self::user_agent(), | |
| 610 | + 'user-agent' => self::USER_AGENT, | |
| 730 | 611 | ) |
| 731 | 612 | ); |
| 732 | 613 | if ( is_wp_error( $res ) ) { |
| 733 | 614 | // Record WHY, don't just vanish. "Unreachable" and "valid but |