PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / trunk
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN vtrunk
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 1.2.0 1.2.1 1.2.2 1.2.3
xspeed / includes / advanced-cache.php

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

450 lines 25.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 * XSPEED_DROPIN_VERSION: 8
5 * Drop-in cache loader. Serves cached HTML before WordPress fully boots.
6 *
7 * Bump XSPEED_DROPIN_VERSION whenever this file's serve logic changes so
8 * Cache::ensure_dropin_current() reinstalls it on existing sites (the
9 * "is it ours?" marker alone can't tell an old copy from a new one).
10 * v2: read .meta on the fast path — replay 404 status + feed Content-Type
11 * and honor per-content TTL (FBS-82406, FBS-82407).
12 * v3: conditional GET — emit Last-Modified + ETag, answer matching
13 * If-Modified-Since / If-None-Match with 304 (FBS-82407 #5).
14 * v4: bail when the `.maintenance-active` sentinel is present so a page
15 * cached while live isn't served during maintenance (FBS-82409 B1).
16 * v5: per-site cache buckets — entries moved from `cache/xspeed/<md5>.html`
17 * to `cache/xspeed/<host>[/<blog-path>]/<md5>.html` so a multisite
18 * purge can be scoped to one blog. An un-bumped drop-in would keep
19 * reading the old flat path, miss every entry and boot WordPress on
20 * every request (#6).
21 * v6: serve tracking-param requests from the fast path — read the
22 * precompiled `ignored_query_params` allow-list instead of bailing on
23 * any query string (#13). An un-bumped drop-in keeps the old bail and
24 * campaign traffic keeps paying a full WordPress boot.
25 * v7: the page TTL is baked in at install time from the `cache_expiry`
26 * setting instead of a hardcoded 86400, so the drop-in enforces the
27 * configured lifetime rather than a fixed 24h (#240).
28 * v8: never serve an empty, stale, or short `.br` sibling — an uninflatable
29 * brotli stream renders as a blank page. THIS FILE IS A COPY made when
30 * caching was enabled, so without the bump an updated site keeps the old
31 * serve logic and never receives the fix (#286).
32 *
33 * IMPORTANT: This file is included by wp-settings.php BEFORE
34 * wp-includes/formatting.php and wp-includes/load.php are loaded, so NO
35 * WordPress functions (sanitize_text_field, wp_unslash, is_admin,
36 * HOUR_IN_SECONDS, etc.) are available here. Use raw PHP only.
37 *
38 * @package XSpeed
39 */
40
41 if ( ! defined( 'ABSPATH' ) ) {
42 exit;
43 }
44
45 // Only handle plain GET requests.
46 // 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.
47 $xspeed_method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) : '';
48 if ( 'GET' !== $xspeed_method ) {
49 return;
50 }
51
52 // Query-string requests. A tracking param contributes nothing to the
53 // response, and PHP already caches `/post?utm_source=x` under the same key
54 // as `/post` — but this file used to bail on ANY query string, so every
55 // visitor arriving from an email or ad campaign paid a full WordPress boot
56 // to be handed a file that was already on disk. On a marketing site that is
57 // most of the paid traffic taking the slowest path. (#13)
58 //
59 // We cannot read the option or call Glob_Matcher here (WordPress is not
60 // loaded), so Cache::sync_query_allowlist() precompiles the user's
61 // `ignored_query_params` into a regex next to the cache files. Every key
62 // must match it; one that doesn't means the response could genuinely vary,
63 // so we stand down and let PHP decide. A missing sidecar means the same —
64 // fail safe, never guess.
65 if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
66 $xspeed_allow_file = WP_CONTENT_DIR . '/cache/xspeed/.ignored-query-params';
67 if ( ! is_readable( $xspeed_allow_file ) ) {
68 return;
69 }
70 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.PHP.NoSilencedErrors.Discouraged -- pre-WP drop-in; an unreadable sidecar degrades to "let PHP handle it".
71 $xspeed_allow_re = trim( (string) @file_get_contents( $xspeed_allow_file ) );
72 if ( '' === $xspeed_allow_re ) {
73 return;
74 }
75
76 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs pre-WP. parse_str() urldecodes exactly as WordPress does; only KEYS are consumed, and only as preg_match() input — never echoed, never executed.
77 parse_str( str_replace( "\0", '', (string) $_SERVER['QUERY_STRING'] ), $xspeed_qs_params );
78 if ( empty( $xspeed_qs_params ) ) {
79 return;
80 }
81 foreach ( array_keys( $xspeed_qs_params ) as $xspeed_qs_key ) {
82 // Anchored: a param named `referrer` must not be waved through by
83 // a `ref` entry. Mirrors Glob_Matcher's full-string semantics.
84 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- a malformed baked pattern degrades to "let PHP handle it", never a warning per request.
85 if ( 1 !== @preg_match( '#^' . $xspeed_allow_re . '$#', (string) $xspeed_qs_key ) ) {
86 return;
87 }
88 }
89 }
90
91 // Honor explicit bypass header. xSpeed's own benchmark REST endpoint
92 // sends `X-XSpeed-Bypass: 1` so we can measure uncached TTFB for the
93 // before/after comparison on the dashboard. Harmless if a third party
94 // sends it — they just get an uncached response.
95 // 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.
96 if ( ! empty( $_SERVER['HTTP_X_XSPEED_BYPASS'] ) ) {
97 return;
98 }
99
100 // Maintenance / coming-soon sentinel. The Pro Maintenance-Cache module writes
101 // `.maintenance-active` next to the cache files whenever the site enters
102 // maintenance / coming-soon mode, and removes it on recovery. The write-side
103 // veto alone can't stop a page cached while the site was live from being
104 // served here (this drop-in runs before WordPress loads), so we bail out and
105 // let WordPress render the maintenance / coming-soon screen instead of serving
106 // a stale real-site page. (FBS-82409 B1)
107 if ( file_exists( WP_CONTENT_DIR . '/cache/xspeed/.maintenance-active' ) ) {
108 return;
109 }
110
111 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
112 return;
113 }
114
115 // Raw-PHP sanitization: strip null bytes only. This value is used for
116 // substring comparisons and as input to md5() — never echoed, never
117 // executed, never written to disk as data. Magic quotes was removed in
118 // PHP 5.4 and the plugin requires PHP 7.4+, so no unslashing is needed.
119 // 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.
120 $xspeed_request_uri = str_replace( "\0", '', (string) $_SERVER['REQUEST_URI'] );
121
122 // Skip admin / login requests.
123 if ( false !== strpos( $xspeed_request_uri, '/wp-admin' ) || false !== strpos( $xspeed_request_uri, '/wp-login' ) ) {
124 return;
125 }
126
127 // Skip logged-in users and comment authors — never serve a cached page to
128 // someone who has a session cookie. Reading raw cookies; we only inspect
129 // names, not values.
130 if ( ! empty( $_COOKIE ) ) {
131 foreach ( $_COOKIE as $xspeed_cookie_name => $xspeed_cookie_value ) {
132 unset( $xspeed_cookie_value );
133 $xspeed_cookie_name = (string) $xspeed_cookie_name;
134 if ( 0 === strpos( $xspeed_cookie_name, 'wordpress_logged_in' )
135 || 0 === strpos( $xspeed_cookie_name, 'comment_author_' )
136 || 0 === strpos( $xspeed_cookie_name, 'wp-postpass_' )
137 // The generic bypass cookie PHP sets whenever it decides a
138 // visitor must not be served from cache (Server_Rules::
139 // BYPASS_COOKIE). Covers repeat visitors even when the baked
140 // rules below are stale.
141 || 'wordpress_no_cache' === $xspeed_cookie_name ) {
142 return;
143 }
144 }
145 }
146
147 // The user's own excluded-cookie list, baked in at install time by
148 // Cache::install_dropin() (the token is replaced with an escaped regex
149 // built by Server_Rules). The drop-in runs before WordPress loads and so
150 // cannot read the settings itself; without this, every cart / membership
151 // / custom cookie rule applied only while a page was cold, and a warm
152 // page was served to exactly the visitors the settings excluded.
153 //
154 // An un-substituted token means the drop-in was copied straight from a
155 // source checkout — fall back to serving nothing from the fast path
156 // rather than treating the literal token as a pattern.
157 $xspeed_cookie_re = '@@XSPEED_COOKIE_RE@@';
158 if ( '@@' !== substr( $xspeed_cookie_re, 0, 2 ) && '' !== $xspeed_cookie_re && ! empty( $_COOKIE ) ) {
159 foreach ( array_keys( $_COOKIE ) as $xspeed_cookie_name ) {
160 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- a malformed baked pattern must degrade to "don't serve from cache", never warn on every request.
161 if ( 1 === @preg_match( '#(' . $xspeed_cookie_re . ')#i', (string) $xspeed_cookie_name ) ) {
162 return;
163 }
164 }
165 }
166
167 // Same for the user-agent bypass list. This is the rule the bypass cookie
168 // can never cover: a bot's very first request to a warm page never
169 // reaches PHP, so there is no earlier request in which to set a cookie.
170 $xspeed_ua_re = '@@XSPEED_UA_RE@@';
171 if ( '@@' !== substr( $xspeed_ua_re, 0, 2 ) && '' !== $xspeed_ua_re ) {
172 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Drop-in runs pre-WP. Value is only matched against a baked, pre-escaped regex; never echoed or executed.
173 $xspeed_ua_raw = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : '';
174 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- see above; degrade to bypass rather than warn.
175 if ( '' !== $xspeed_ua_raw && 1 === @preg_match( '#(' . $xspeed_ua_re . ')#i', $xspeed_ua_raw ) ) {
176 return;
177 }
178 }
179
180 // 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.
181 $xspeed_host = isset( $_SERVER['HTTP_HOST'] ) ? (string) $_SERVER['HTTP_HOST'] : 'default';
182 $xspeed_host = str_replace( "\0", '', $xspeed_host );
183 // Restrict host to a safe charset (letters, digits, dot, hyphen, colon for port).
184 $xspeed_host = preg_replace( '/[^a-zA-Z0-9.\-:]/', '', $xspeed_host );
185
186 $xspeed_path_only = strtok( $xspeed_request_uri, '?' );
187
188 // Device bucket — MUST mirror XSpeed\Cache::cache_key() exactly, or the key
189 // the drop-in computes won't match the file Cache::store() wrote, the HIT
190 // branch below never fires, and every request falls through to a full
191 // WordPress boot (defeating the whole point of the pre-WP drop-in).
192 //
193 // Cache::cache_key() appends '|m' / '|d' when the cache module's
194 // `mobile_separate` setting is on. The drop-in can't read WP options
195 // (it runs before WordPress loads), so Cache writes a zero-byte sidecar
196 // flag — `.mobile-separate` next to the cache files — whenever that setting
197 // is on, and removes it when off (see Cache::sync_mobile_flag()). We mirror
198 // the same UA token list wp_is_mobile() uses, the same one Cache's inline
199 // fallback detector uses.
200 $xspeed_device = '';
201 if ( file_exists( WP_CONTENT_DIR . '/cache/xspeed/.mobile-separate' ) ) {
202 // Mirror core's wp_is_mobile() EXACTLY (which Cache::is_mobile_request()
203 // defers to): check the Sec-CH-UA-Mobile client hint first, then fall
204 // back to the same UA token list. Any divergence from the engine's
205 // detection re-introduces the key mismatch this whole flag exists to
206 // prevent.
207 $xspeed_is_mobile = false;
208 if ( isset( $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ) ) {
209 // 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.
210 $xspeed_is_mobile = ( '?1' === $_SERVER['HTTP_SEC_CH_UA_MOBILE'] );
211 } else {
212 // 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.
213 $xspeed_ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : '';
214 $xspeed_is_mobile = (bool) preg_match( '/(Mobile|Android|Silk\/|Kindle|BlackBerry|Opera Mini|Opera Mobi)/i', $xspeed_ua );
215 }
216 $xspeed_device = $xspeed_is_mobile ? '|m' : '|d';
217 }
218
219 $xspeed_cache_key = md5( $xspeed_host . $xspeed_path_only . $xspeed_device );
220
221 // Per-site bucket. MUST mirror XSpeed\Cache::current_host_dir() exactly —
222 // same charset, same trimmed dots, same 'default' fallback, same multisite
223 // path prefix — or the drop-in looks in a directory Cache::store() never
224 // wrote to, every HIT misses, and every request falls through to a full
225 // WordPress boot.
226 //
227 // Note this is NOT $xspeed_host: the cache KEY keeps the colon of
228 // `host:port` (it only ever feeds md5()), while the DIRECTORY cannot —
229 // a colon is not portable in a path. (#6)
230 $xspeed_host_dir = $xspeed_host;
231 $xspeed_host_colon = strpos( $xspeed_host_dir, ':' );
232 if ( false !== $xspeed_host_colon ) {
233 $xspeed_host_dir = substr( $xspeed_host_dir, 0, $xspeed_host_colon );
234 }
235 $xspeed_host_dir = preg_replace( '/[^a-zA-Z0-9.\-]/', '', $xspeed_host_dir );
236 $xspeed_host_dir = preg_replace( '/\.{2,}/', '.', (string) $xspeed_host_dir );
237 $xspeed_host_dir = trim( (string) $xspeed_host_dir, '.-' );
238 if ( '' === $xspeed_host_dir ) {
239 $xspeed_host_dir = 'default';
240 }
241
242 // Subdirectory multisite: every blog shares one host, so the host alone
243 // would put them all in one bucket and they would keep purging each other.
244 // We cannot call is_multisite()/get_blog_details() here (WordPress is not
245 // loaded), so Cache::sync_site_paths() persists the network's blog paths
246 // as `<raw-path>|<segment>` lines, longest first. Prefix-match the URI.
247 $xspeed_paths_file = WP_CONTENT_DIR . '/cache/xspeed/.site-paths';
248 if ( file_exists( $xspeed_paths_file ) ) {
249 $xspeed_uri_trimmed = ltrim( (string) $xspeed_path_only, '/' );
250 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- our own sidecar; WP_Filesystem is not loaded pre-WP.
251 $xspeed_paths_raw = (string) @file_get_contents( $xspeed_paths_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- unreadable sidecar just means "no prefix".
252 foreach ( explode( "\n", $xspeed_paths_raw ) as $xspeed_path_line ) {
253 $xspeed_sep = strpos( $xspeed_path_line, '|' );
254 if ( false === $xspeed_sep ) {
255 continue;
256 }
257 $xspeed_raw_path = substr( $xspeed_path_line, 0, $xspeed_sep );
258 $xspeed_segment = substr( $xspeed_path_line, $xspeed_sep + 1 );
259 if ( '' === $xspeed_raw_path || '' === $xspeed_segment ) {
260 continue;
261 }
262 if ( $xspeed_uri_trimmed === $xspeed_raw_path
263 || 0 === strpos( $xspeed_uri_trimmed, $xspeed_raw_path . '/' ) ) {
264 $xspeed_host_dir .= '/' . $xspeed_segment;
265 break;
266 }
267 }
268 }
269
270 $xspeed_cache_dir = WP_CONTENT_DIR . '/cache/xspeed/' . $xspeed_host_dir . '/';
271 $xspeed_cache_file = $xspeed_cache_dir . $xspeed_cache_key . '.html';
272 $xspeed_meta_file = $xspeed_cache_dir . $xspeed_cache_key . '.meta';
273
274 if ( file_exists( $xspeed_cache_file ) ) {
275 // Read the .meta sidecar (status / content_type / ttl) the same way the
276 // PHP HIT path does — the drop-in serves cached feeds and 404s too, so it
277 // must replay their Content-Type / status and honor their per-content TTL.
278 // Ordinary 200 text/html pages have no .meta (the common path stays fast).
279 // (FBS-82406 soft-404, FBS-82407 feed content-type + TTL)
280 $xspeed_meta = array();
281 if ( file_exists( $xspeed_meta_file ) ) {
282 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- pre-WP drop-in; one tiny JSON sidecar.
283 $xspeed_meta_raw = file_get_contents( $xspeed_meta_file );
284 if ( false !== $xspeed_meta_raw ) {
285 $xspeed_decoded = json_decode( $xspeed_meta_raw, true );
286 if ( is_array( $xspeed_decoded ) ) {
287 $xspeed_meta = $xspeed_decoded;
288 }
289 }
290 }
291
292 // Per-content TTL from meta (e.g. feeds) falls back to the site's
293 // configured cache_expiry, baked in at install time by
294 // Cache::install_dropin() and re-baked on every settings save. The
295 // drop-in runs before WordPress loads and so cannot read the option
296 // itself; without this it applied a hardcoded 24h to every ordinary
297 // page — write_meta() only writes a `ttl` sidecar when the value differs
298 // from the page default, so ordinary pages carry no sidecar at all.
299 // That served stale content under Conservative (12h) and refused the
300 // fast path for 6 of 7 days under Aggressive (168h). (#240)
301 //
302 // An un-substituted token means the drop-in was copied straight from a
303 // source checkout — fall back to the historical 24h literal rather than
304 // treating the token as a number. HOUR_IN_SECONDS isn't defined yet.
305 $xspeed_default_ttl = '@@XSPEED_DEFAULT_TTL@@';
306 $xspeed_unbaked = ( '@@' === substr( $xspeed_default_ttl, 0, 2 ) || (int) $xspeed_default_ttl < 1 );
307 $xspeed_default_ttl = $xspeed_unbaked ? 86400 : (int) $xspeed_default_ttl;
308 if ( $xspeed_unbaked ) {
309 // Make the un-substituted state observable. Serving the 24h literal
310 // silently is exactly how the original bug stayed invisible; a site
311 // on this path is enforcing a lifetime nobody configured.
312 header( 'X-XSpeed-Cache-TTL: default (unbaked)' );
313 }
314
315 $xspeed_ttl = ( isset( $xspeed_meta['ttl'] ) && (int) $xspeed_meta['ttl'] > 0 ) ? (int) $xspeed_meta['ttl'] : $xspeed_default_ttl;
316 $xspeed_age = time() - filemtime( $xspeed_cache_file );
317 if ( $xspeed_age < $xspeed_ttl ) {
318 // PHP-served cache hit (the ~85ms fallback path). The nginx static
319 // rewrite sends "HIT (nginx)" for the fast 5-15ms path; same header,
320 // distinct value so you can tell which layer served the page.
321 header( 'X-XSpeed-Cache: HIT (php)' );
322
323 // Record the HIT for the dashboard hit-ratio. The drop-in runs
324 // BEFORE WordPress loads, so it can't call Hit_Counter — instead
325 // it appends one line to the same hits.log the nginx static path
326 // uses, and Hit_Counter::collect_nginx_log_hits() drains + counts
327 // both on the next dashboard load. Without this, every drop-in HIT
328 // was served but never counted, so the hit ratio sat at 0.
329 // Best-effort: a failed append must never break serving the page.
330 //
331 // Path is baked in at install time by Cache::install_dropin(), which
332 // replaces the @@XSPEED_HITS_LOG@@ token on the next line with the
333 // resolved absolute path (uploads/xspeed/hits.log — NOT the cache dir,
334 // which gets deleted on purge/uninstall and would take nginx down,
335 // FBS-82478). The default below is the fallback for an un-substituted
336 // drop-in (e.g. run straight from a dev source checkout); the installed
337 // copy always carries the absolute uploads path.
338 $xspeed_hits_log = '@@XSPEED_HITS_LOG@@'; // replaced at install
339 if ( '@@' === substr( $xspeed_hits_log, 0, 2 ) ) {
340 $xspeed_hits_log = WP_CONTENT_DIR . '/uploads/xspeed/hits.log';
341 }
342 // 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).
343 @file_put_contents( $xspeed_hits_log, "hit\n", FILE_APPEND | LOCK_EX );
344
345 // Replay the cached response's status + content-type from .meta, so a
346 // cached 404 serves 404 (not a soft-404 200) and a cached feed serves
347 // application/rss+xml (not text/html). (FBS-82406, FBS-82407)
348 if ( ! empty( $xspeed_meta['status'] ) && function_exists( 'http_response_code' ) ) {
349 http_response_code( (int) $xspeed_meta['status'] );
350 }
351 if ( ! empty( $xspeed_meta['content_type'] ) && is_string( $xspeed_meta['content_type'] ) ) {
352 header( 'Content-Type: ' . $xspeed_meta['content_type'] );
353 }
354
355 // Conditional GET: Last-Modified + ETag from the cache file's mtime,
356 // answer a matching If-Modified-Since / If-None-Match with 304 so
357 // aggregators skip re-downloading an unchanged cached feed/page.
358 // (FBS-82407 #5)
359 $xspeed_mtime = (int) filemtime( $xspeed_cache_file );
360 if ( $xspeed_mtime > 0 ) {
361 $xspeed_lastmod = gmdate( 'D, d M Y H:i:s', $xspeed_mtime ) . ' GMT';
362 $xspeed_etag = '"' . md5( $xspeed_cache_file . '|' . $xspeed_mtime ) . '"';
363 header( 'Last-Modified: ' . $xspeed_lastmod );
364 header( 'ETag: ' . $xspeed_etag );
365 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- pre-WP drop-in; values only compared to a server-generated etag / parsed as a date, never echoed or executed.
366 $xspeed_inm = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? trim( (string) $_SERVER['HTTP_IF_NONE_MATCH'] ) : '';
367 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- as above.
368 $xspeed_ims = isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ? trim( (string) $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) : '';
369 if ( ( '' !== $xspeed_inm && false !== strpos( $xspeed_inm, $xspeed_etag ) )
370 || ( '' !== $xspeed_ims && false !== ( $xspeed_ims_ts = strtotime( $xspeed_ims ) ) && $xspeed_ims_ts >= $xspeed_mtime ) ) {
371 if ( function_exists( 'http_response_code' ) ) {
372 http_response_code( 304 );
373 }
374 exit;
375 }
376 }
377
378 // Serve the precompressed Brotli sibling when the client accepts it
379 // and the Pro Brotli module wrote <file>.br. MUST mirror
380 // XSpeed\Cache::maybe_serve_brotli() on the non-drop-in serve path —
381 // both decide on the same Accept-Encoding token match + sibling
382 // existence, so the response is identical whichever path serves.
383 // pre-WP: no sanitize_text_field()/wp_unslash(); the value is only
384 // lowercased + regex-matched, never echoed.
385 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- pre-WP drop-in; value is only lowercased + token-matched, never echoed or executed.
386 $xspeed_accept_enc = isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ? strtolower( str_replace( "\0", '', (string) $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) : '';
387 $xspeed_br_file = $xspeed_cache_file . '.br';
388
389 // Existence is NOT enough: an empty or stale sibling is unservable.
390 // Mirrors XSpeed\Cache::brotli_sibling_is_usable(); inlined because
391 // this file runs before WordPress and cannot call it.
392 //
393 // Deliberately NO size-ratio floor. Brotli's ratio is unbounded on
394 // repetitive input — a ~1 MB page of table rows compresses to about
395 // 0.04% — so a floor rejects genuinely good siblings and silently
396 // serves the uncompressed page.
397 //
398 // Truncation is instead caught exactly, from the byte count the
399 // writer recorded in `<file>.br.size` when it published the sibling.
400 // A stream shorter than its own declared length cannot inflate; one
401 // that matches was published whole. Where no record exists — a
402 // sibling written before this version, which is precisely the
403 // already-broken file sitting on a live site right now — the checks
404 // below still apply and the atomic writer stops new ones appearing.
405 $xspeed_br_ok = false;
406 if ( is_readable( $xspeed_br_file ) ) {
407 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- pre-WP drop-in; a stat failure means "don't serve it", handled by the size checks.
408 $xspeed_br_size = (int) @filesize( $xspeed_br_file );
409 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- as above.
410 $xspeed_html_size = (int) @filesize( $xspeed_cache_file );
411 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- as above.
412 $xspeed_br_mtime = (int) @filemtime( $xspeed_br_file );
413
414 // MUST mirror XSpeed\Cache::brotli_expected_size(); 0 means "no
415 // record", never "zero bytes".
416 $xspeed_br_expected = 0;
417 $xspeed_br_sidecar = $xspeed_br_file . '.size';
418 if ( is_readable( $xspeed_br_sidecar ) ) {
419 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents,WordPress.PHP.NoSilencedErrors.Discouraged -- pre-WP drop-in; an unreadable sidecar means "unknown", handled by the cast.
420 $xspeed_br_expected = (int) trim( (string) @file_get_contents( $xspeed_br_sidecar ) );
421 if ( $xspeed_br_expected < 0 ) {
422 $xspeed_br_expected = 0;
423 }
424 }
425
426 $xspeed_br_ok = $xspeed_br_size > 0
427 && $xspeed_html_size > 0
428 // Not stale: a sibling older than the page would serve the
429 // previous revision under the current entry's ETag.
430 && ( $xspeed_br_mtime <= 0 || $xspeed_mtime <= 0 || $xspeed_br_mtime >= $xspeed_mtime )
431 // Not truncated, where the writer left a length to check.
432 && ( $xspeed_br_expected <= 0 || $xspeed_br_size === $xspeed_br_expected );
433 }
434
435 if ( preg_match( '/(^|[\s,])br([\s,;]|$)/', $xspeed_accept_enc )
436 && $xspeed_br_ok ) {
437 header( 'Content-Encoding: br' );
438 header( 'Vary: Accept-Encoding', false );
439 header_remove( 'Content-Length' );
440 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Drop-in runs before WP_Filesystem is available; readfile streams the precompressed sibling directly.
441 readfile( $xspeed_br_file );
442 exit;
443 }
444
445 // 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.
446 readfile( $xspeed_cache_file );
447 exit;
448 }
449 }
450