| @@ -88,13 +88,20 @@ | ||
| 88 | 88 | self::flush_pending(); |
| 89 | 89 | } |
| 90 | 90 | |
| 91 | 91 | /** |
| 92 | + * The bot / crawler / scanner alternation, without delimiters so the | |
| 93 | + * drop-in can compose it — see excluded_ua_regex(). | |
| 94 | + */ | |
| 95 | + public const BOT_UA_PATTERN = 'bot|crawl|spider|slurp|scan|curl|wget|python-requests|python-urllib|libwww|httpclient|go-http|okhttp|axios|node-fetch|headless|phantomjs|masscan|nikto|sqlmap|zgrab|semrush|ahrefs|mj12|dotbot|petalbot|bytespider|facebookexternalhit|preview|monitor|uptime|pingdom|gtmetrix|lighthouse|pagespeed'; | |
| 96 | + | |
| 97 | + /** | |
| 92 | 98 | * Whether a User-Agent is a known bot / crawler / vulnerability scanner — |
| 93 | 99 | * its cache misses are cache-warming or hostile noise, not a signal of how |
| 94 | 100 | * the cache serves real visitors. Deliberately broad: matches the common |
| 95 | 101 | * crawler tokens plus the generic markers scanners and libraries carry. |
| 96 | - * Pure + unit-tested. (#118) | |
| 102 | + * Unit-tested; no longer pure — Self_Traffic::is_self() runs the | |
| 103 | + * xspeed_self_user_agents filter, so the answer can vary per site. (#118) | |
| 97 | 104 | */ |
| 98 | 105 | public static function is_bot_ua( string $ua ): bool { |
| 99 | 106 | if ( '' === $ua ) { |
| 100 | 107 | // No UA at all is overwhelmingly automated traffic, not a browser. |
| @@ -99,12 +106,37 @@ | ||
| 99 | 106 | if ( '' === $ua ) { |
| 100 | 107 | // No UA at all is overwhelmingly automated traffic, not a browser. |
| 101 | 108 | return true; |
| 102 | 109 | } |
| 103 | - return 1 === preg_match( | |
| 104 | - '~(bot|crawl|spider|slurp|scan|curl|wget|python-requests|python-urllib|libwww|httpclient|go-http|okhttp|axios|node-fetch|headless|phantomjs|masscan|nikto|sqlmap|zgrab|semrush|ahrefs|mj12|dotbot|petalbot|bytespider|facebookexternalhit|preview|monitor|uptime|pingdom|gtmetrix|lighthouse|pagespeed)~i', | |
| 105 | - $ua | |
| 106 | - ); | |
| 110 | + // Our own warmer, benchmark and verifier are warming the cache, not | |
| 111 | + // visiting it: `xSpeed-Warmer`, `xSpeed Benchmark`, and the rest. | |
| 112 | + // Callers with a request also check Self_Traffic::request_is_marked(). | |
| 113 | + if ( Self_Traffic::is_self( $ua ) ) { | |
| 114 | + return true; | |
| 115 | + } | |
| 116 | + return 1 === preg_match( '~(' . self::BOT_UA_PATTERN . ')~i', $ua ); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * The "do not count this user agent" alternation: bots and scanners, | |
| 121 | + * plus the fragments xSpeed's own requests carry. A renamed warmer is | |
| 122 | + * not in it on purpose; that request is recognised by | |
| 123 | + * Self_Traffic::HEADER, because its UA may be a real browser's. | |
| 124 | + * | |
| 125 | + * Baked into the drop-in at install time (`@@XSPEED_HIT_EXCLUDE_RE@@`). | |
| 126 | + * The drop-in runs before WordPress, so it cannot ask this class and the | |
| 127 | + * hits.log line it writes carries no user agent — nothing downstream can | |
| 128 | + * reclassify the line later, which is why the decision has to travel | |
| 129 | + * with the file. A hardcoded copy of the fragments drifted instead: it | |
| 130 | + * excluded the warmer but still counted every crawler HIT, and it could | |
| 131 | + * not know about an overridden `xspeed_preloader_user_agent`. | |
| 132 | + */ | |
| 133 | + public static function excluded_ua_regex(): string { | |
| 134 | + $parts = array( self::BOT_UA_PATTERN ); | |
| 135 | + foreach ( Self_Traffic::agents() as $agent ) { | |
| 136 | + $parts[] = preg_quote( $agent, '#' ); | |
| 137 | + } | |
| 138 | + return implode( '|', $parts ); | |
| 107 | 139 | } |
| 108 | 140 | |
| 109 | 141 | public static function record_miss(): void { |
| 110 | 142 | ++self::$pending['miss']; |