PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.5
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.5
1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
xspeed / includes / advanced-cache.php

advanced-cache.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.0.5, at includes/advanced-cache.php

149 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * XSPEED_DROPIN
4 * Drop-in cache loader. Serves cached HTML before WordPress fully boots.
5 *
6 * IMPORTANT: This file is included by wp-settings.php BEFORE
7 * wp-includes/formatting.php and wp-includes/load.php are loaded, so NO
8 * WordPress functions (sanitize_text_field, wp_unslash, is_admin,
9 * HOUR_IN_SECONDS, etc.) are available here. Use raw PHP only.
10 *
11 * @package XSpeed
12 */
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 // Only handle plain GET requests.
19 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs before wp-includes/formatting.php loads, so wp_unslash() and sanitize_text_field() are unavailable. Value is upper-cased and matched against the literal string 'GET'; never echoed, never executed.
20 $xspeed_method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) : '';
21 if ( 'GET' !== $xspeed_method ) {
22 return;
23 }
24
25 // Skip cached query-string requests (search, pagination via ?, etc.).
26 if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
27 return;
28 }
29
30 // Honor explicit bypass header. xSpeed's own benchmark REST endpoint
31 // sends `X-XSpeed-Bypass: 1` so we can measure uncached TTFB for the
32 // before/after comparison on the dashboard. Harmless if a third party
33 // sends it — they just get an uncached response.
34 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs before WP loads. Value is only used as an isset() check + literal string comparison, never echoed.
35 if ( ! empty( $_SERVER['HTTP_X_XSPEED_BYPASS'] ) ) {
36 return;
37 }
38
39 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
40 return;
41 }
42
43 // Raw-PHP sanitization: strip null bytes only. This value is used for
44 // substring comparisons and as input to md5() — never echoed, never
45 // executed, never written to disk as data. Magic quotes was removed in
46 // PHP 5.4 and the plugin requires PHP 7.4+, so no unslashing is needed.
47 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs before wp_unslash()/sanitize_text_field() are loaded; null-byte strip is the strongest sanitizer available pre-WP-bootstrap. Value is only used for substring comparison and as md5() input.
48 $xspeed_request_uri = str_replace( "\0", '', (string) $_SERVER['REQUEST_URI'] );
49
50 // Skip admin / login requests.
51 if ( false !== strpos( $xspeed_request_uri, '/wp-admin' ) || false !== strpos( $xspeed_request_uri, '/wp-login' ) ) {
52 return;
53 }
54
55 // Skip logged-in users and comment authors — never serve a cached page to
56 // someone who has a session cookie. Reading raw cookies; we only inspect
57 // names, not values.
58 if ( ! empty( $_COOKIE ) ) {
59 foreach ( $_COOKIE as $xspeed_cookie_name => $xspeed_cookie_value ) {
60 unset( $xspeed_cookie_value );
61 $xspeed_cookie_name = (string) $xspeed_cookie_name;
62 if ( 0 === strpos( $xspeed_cookie_name, 'wordpress_logged_in' )
63 || 0 === strpos( $xspeed_cookie_name, 'comment_author_' )
64 || 0 === strpos( $xspeed_cookie_name, 'wp-postpass_' ) ) {
65 return;
66 }
67 }
68 }
69
70 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs before wp_unslash()/sanitize_text_field() are loaded. Value is filtered through a strict allowlist regex below (letters, digits, dot, hyphen, colon) and only used as md5() input for the cache key.
71 $xspeed_host = isset( $_SERVER['HTTP_HOST'] ) ? (string) $_SERVER['HTTP_HOST'] : 'default';
72 $xspeed_host = str_replace( "\0", '', $xspeed_host );
73 // Restrict host to a safe charset (letters, digits, dot, hyphen, colon for port).
74 $xspeed_host = preg_replace( '/[^a-zA-Z0-9.\-:]/', '', $xspeed_host );
75
76 $xspeed_path_only = strtok( $xspeed_request_uri, '?' );
77
78 // Device bucket — MUST mirror XSpeed\Cache::cache_key() exactly, or the key
79 // the drop-in computes won't match the file Cache::store() wrote, the HIT
80 // branch below never fires, and every request falls through to a full
81 // WordPress boot (defeating the whole point of the pre-WP drop-in).
82 //
83 // Cache::cache_key() appends '|m' / '|d' when the cache module's
84 // `mobile_separate` setting is on. The drop-in can't read WP options
85 // (it runs before WordPress loads), so Cache writes a zero-byte sidecar
86 // flag — `.mobile-separate` next to the cache files — whenever that setting
87 // is on, and removes it when off (see Cache::sync_mobile_flag()). We mirror
88 // the same UA token list wp_is_mobile() uses, the same one Cache's inline
89 // fallback detector uses.
90 $xspeed_device = '';
91 if ( file_exists( WP_CONTENT_DIR . '/cache/xspeed/.mobile-separate' ) ) {
92 // Mirror core's wp_is_mobile() EXACTLY (which Cache::is_mobile_request()
93 // defers to): check the Sec-CH-UA-Mobile client hint first, then fall
94 // back to the same UA token list. Any divergence from the engine's
95 // detection re-introduces the key mismatch this whole flag exists to
96 // prevent.
97 $xspeed_is_mobile = false;
98 if ( isset( $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ) ) {
99 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs pre-WP. Value is compared against the literal '?1', never echoed or executed.
100 $xspeed_is_mobile = ( '?1' === $_SERVER['HTTP_SEC_CH_UA_MOBILE'] );
101 } else {
102 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs before wp_unslash()/sanitize_text_field() load. Value is only matched against a literal token regex, never echoed or executed.
103 $xspeed_ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : '';
104 $xspeed_is_mobile = (bool) preg_match( '/(Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi)/i', $xspeed_ua );
105 }
106 $xspeed_device = $xspeed_is_mobile ? '|m' : '|d';
107 }
108
109 $xspeed_cache_key = md5( $xspeed_host . $xspeed_path_only . $xspeed_device );
110 $xspeed_cache_file = WP_CONTENT_DIR . '/cache/xspeed/' . $xspeed_cache_key . '.html';
111
112 if ( file_exists( $xspeed_cache_file ) ) {
113 // 24h TTL in seconds. HOUR_IN_SECONDS is a WordPress constant defined
114 // after this drop-in loads, so use a literal here.
115 $xspeed_age = time() - filemtime( $xspeed_cache_file );
116 if ( $xspeed_age < 86400 ) {
117 // PHP-served cache hit (the ~85ms fallback path). The nginx static
118 // rewrite sends "HIT (nginx)" for the fast 5-15ms path; same header,
119 // distinct value so you can tell which layer served the page.
120 header( 'X-XSpeed-Cache: HIT (php)' );
121
122 // Record the HIT for the dashboard hit-ratio. The drop-in runs
123 // BEFORE WordPress loads, so it can't call Hit_Counter — instead
124 // it appends one line to the same hits.log the nginx static path
125 // uses, and Hit_Counter::collect_nginx_log_hits() drains + counts
126 // both on the next dashboard load. Without this, every drop-in HIT
127 // was served but never counted, so the hit ratio sat at 0.
128 // Best-effort: a failed append must never break serving the page.
129 //
130 // Path is baked in at install time by Cache::install_dropin(), which
131 // replaces the @@XSPEED_HITS_LOG@@ token on the next line with the
132 // resolved absolute path (uploads/xspeed/hits.log — NOT the cache dir,
133 // which gets deleted on purge/uninstall and would take nginx down,
134 // FBS-82478). The default below is the fallback for an un-substituted
135 // drop-in (e.g. run straight from a dev source checkout); the installed
136 // copy always carries the absolute uploads path.
137 $xspeed_hits_log = '@@XSPEED_HITS_LOG@@'; // replaced at install
138 if ( '@@' === substr( $xspeed_hits_log, 0, 2 ) ) {
139 $xspeed_hits_log = WP_CONTENT_DIR . '/uploads/xspeed/hits.log';
140 }
141 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- pre-WP drop-in; WP_Filesystem isn't loaded. One short line, append + lock; failures are non-fatal (the ratio just under-counts).
142 @file_put_contents( $xspeed_hits_log, "hit\n", FILE_APPEND | LOCK_EX );
143
144 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Drop-in runs before WP_Filesystem is available; readfile is optimal for streaming a static cache file to the visitor.
145 readfile( $xspeed_cache_file );
146 exit;
147 }
148 }
149