PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.1
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 1.2.0 All 28 releases
xspeed / includes / advanced-cache.php

advanced-cache.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.3.1, 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