(string) $cached['verdict'], 'checked_at' => (int) ( $cached['checked_at'] ?? 0 ), ); } self::schedule(); return array( 'verdict' => self::UNKNOWN, 'checked_at' => 0, ); } /** Queue one background run, unless one is pending. */ public static function schedule(): void { if ( ! function_exists( 'wp_next_scheduled' ) || wp_next_scheduled( self::CRON_HOOK ) ) { return; } wp_schedule_single_event( time() + 30, self::CRON_HOOK ); } /** * Run the probe: two requests through whatever is in front of the site. * * @return array{verdict:string,checked_at:int} */ public static function run(): array { $token = function_exists( 'wp_generate_password' ) ? wp_generate_password( 16, false ) : bin2hex( random_bytes( 8 ) ); set_transient( self::TOKEN_TRANSIENT, $token, 2 * MINUTE_IN_SECONDS ); $url = add_query_arg( self::PARAM, $token, home_url( '/' ) ); $first = self::fetch( $url ); $second = null === $first ? null : self::fetch( $url ); delete_transient( self::TOKEN_TRANSIENT ); if ( null === $first || null === $second ) { $result = array( 'verdict' => self::UNKNOWN, 'checked_at' => time(), ); set_transient( self::TRANSIENT, $result, HOUR_IN_SECONDS ); return $result; } $result = array( 'verdict' => self::verdict( $first, $second ), 'checked_at' => time(), ); set_transient( self::TRANSIENT, $result, 12 * HOUR_IN_SECONDS ); return $result; } /** * One request, or null when it failed. * * @param string $url Probe URL. * @return array{ray:string,status:string}|null */ private static function fetch( string $url ): ?array { $res = wp_remote_get( $url, array( 'timeout' => 10, 'redirection' => 0, 'headers' => Self_Traffic::headers( array( 'User-Agent' => 'xSpeed Health Probe/1.0' ) ), 'cookies' => array(), 'sslverify' => ! Cookie_Inspector::is_local_host( home_url( '/' ) ), ) ); if ( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ) { return null; } return array( 'ray' => (string) wp_remote_retrieve_header( $res, 'cf-ray' ), 'status' => (string) wp_remote_retrieve_header( $res, 'cf-cache-status' ), ); } /** * Answer the probe with a small HTML page marked `no-store` everywhere. * Only while a probe is running, and only for its token. */ public static function maybe_answer(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- an anonymous probe; the token below is the check. $given = isset( $_GET[ self::PARAM ] ) ? sanitize_text_field( wp_unslash( $_GET[ self::PARAM ] ) ) : ''; if ( ! self::answers( $given ) ) { return; } self::send_answer(); exit; } /** * Whether a request carrying this token is the running probe. * * @param string $given Token from the query string. */ public static function answers( string $given ): bool { if ( '' === $given ) { return false; } $token = get_transient( self::TOKEN_TRANSIENT ); return is_string( $token ) && '' !== $token && hash_equals( $token, $given ); } /** The probe page's headers and body. */ public static function send_answer(): void { if ( ! headers_sent() ) { status_header( 200 ); header( 'Content-Type: text/html; charset=utf-8' ); header( 'Cache-Control: no-store, private' ); header( 'CDN-Cache-Control: no-store' ); header( 'Cloudflare-CDN-Cache-Control: no-store' ); } echo '