PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.3
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
← All changes | includes/class-gzip.php +55 -9 1.0.71.3.3 View file →
@@ -108,14 +108,34 @@
108 108 return (string) apply_filters( 'xspeed_compression_nginx_snippet', $snippet );
109 109 }
110 110
111 111 /**
112 - * Returns true if the response we just received looks GZIP-encoded.
113 - * Result is cached in a transient for 1 hour because the probe makes a
114 - * full HTTP request to home_url() — too slow to run on every /status hit.
112 + * Does the origin actually serve a GZIP-encoded homepage?
113 + *
114 + * true — proven: the response was gzipped.
115 + * false — proven otherwise: we reached the site and it wasn't.
116 + * null — no verdict: the loopback never completed (firewalled,
117 + * TLS failure, timeout, WAF answering instead of the origin).
118 + *
119 + * The third state is the point (issue #18). The old signature folded
120 + * "couldn't ask" into "answer is no" and cached that for an hour, which
121 + * pinned a permanent "GZIP enabled but not active on the server" warning
122 + * onto sites whose gzip was demonstrably fine — the loopback was the
123 + * only broken thing. Only a *proven* false may nag the user. Mirrors the
124 + * inconclusive/active split already used by the rewrite probe.
125 + *
126 + * A verdict is cached for an hour because the probe costs a full HTTP
127 + * request to home_url() — too slow to run on every /status hit. A
128 + * non-verdict is cached for a minute only, so a transient blip resolves
129 + * itself on the next page load instead of sticking around.
130 + *
131 + * @return bool|null
115 132 */
116 133 public static function probe_active() {
117 134 $cached = get_transient( 'xspeed_gzip_active' );
135 + if ( '?' === $cached ) {
136 + return null;
137 + }
118 138 if ( false !== $cached ) {
119 139 return '1' === $cached;
120 140 }
121 141
@@ -121,17 +141,43 @@
121 141
122 142 $res = wp_remote_get(
123 143 home_url( '/' ),
124 144 array(
125 - 'headers' => array( 'Accept-Encoding' => 'gzip' ),
126 - 'timeout' => 3,
145 + 'headers' => array( 'Accept-Encoding' => 'gzip' ),
146 + // 3s wasn't enough to pull a full homepage on a busy shared
147 + // host, and every timeout used to read as "gzip is broken".
148 + 'timeout' => 5,
149 + // Loopback to our own hostname; staging boxes routinely have
150 + // a self-signed or mismatched cert. Same call the sibling
151 + // probes make (Browser_Cache::probe_headers_present()).
152 + 'sslverify' => false,
153 + // Keep the payload exactly as it came off the wire, so the
154 + // gzip magic-byte fallback below still has something to
155 + // look at if a transport strips Content-Encoding after
156 + // decoding. See Cache_Benchmark::measure().
157 + 'decompress' => false,
127 158 )
128 159 );
129 160
130 - $active = false;
131 - if ( ! is_wp_error( $res ) ) {
132 - $enc = wp_remote_retrieve_header( $res, 'content-encoding' );
133 - $active = is_string( $enc ) && false !== stripos( $enc, 'gzip' );
161 + if ( is_wp_error( $res ) || 200 !== (int) wp_remote_retrieve_response_code( $res ) ) {
162 + set_transient( 'xspeed_gzip_active', '?', MINUTE_IN_SECONDS );
163 + return null;
164 + }
165 +
166 + // wp_remote_retrieve_header() hands back a STRING for a single
167 + // header but an ARRAY when it appears twice — routine behind a
168 + // CDN or proxy. The old is_string() test threw those away and
169 + // reported "not gzipped". (Same trap as FBS-82141.)
170 + $raw = wp_remote_retrieve_header( $res, 'content-encoding' );
171 + $enc = is_array( $raw ) ? implode( ', ', $raw ) : (string) $raw;
172 +
173 + $active = false !== stripos( $enc, 'gzip' );
174 + if ( ! $active ) {
175 + // Header absent — a transport that decodes and drops it would
176 + // look identical to a server that never compressed. The raw
177 + // body settles it: a gzip stream starts with 0x1f 0x8b.
178 + $body = wp_remote_retrieve_body( $res );
179 + $active = is_string( $body ) && 0 === strncmp( $body, "\x1f\x8b", 2 );
134 180 }
135 181
136 182 set_transient( 'xspeed_gzip_active', $active ? '1' : '0', HOUR_IN_SECONDS );
137 183 return $active;