Skip to content

wpsc_get_accept_header() ignores Accept header quality values (q-values) #1045

@LorenDorez

Description

@LorenDorez

wpsc_get_accept_header() in wp-cache-phase2.php uses a naive str_contains() check to detect JSON requests. It does not parse Accept header quality values (q=) as defined in RFC 7231 §5.3.2. As a result, any request where application/json appears anywhere in the Accept header — even at low priority — is misclassified as a JSON request, preventing the cached file from being served and triggering a cache rebuild.

This is reproducible with New Relic Synthetics, which sends:

Accept: text/html,application/xhtml+xml,application/json;q=0.9,application/javascript;q=0.9,text/javascript;q=0.9,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7

This is a valid HTML-preferring Accept header — text/html has implicit q=1.0 (highest priority), and application/json is explicitly deprioritised at q=0.9. However, WPSC treats the entire request as a JSON request and refuses to serve the cached file.


In wp-cache-phase2.php, wpsc_get_accept_header() (around line 533):

$accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? strtolower( filter_var( $_SERVER['HTTP_ACCEPT'] ) ) : '';

foreach ( $accept_headers as $header ) {
    if ( str_contains( $accept, $header ) ) {
        $accept = 'application/json';
    }
}

if ( $accept !== 'application/json' ) {
    $accept = 'text/html';
}

The str_contains() check finds application/json anywhere in the raw Accept string without considering q-values. A request from New Relic Synthetics with application/json;q=0.9 (lower priority than text/html) is classified as a JSON request.

This misclassification has two downstream effects:

  1. wp_cache_serve_cache_file() returns false immediately because wpsc_get_accept_header() !== 'text/html', so the cached file is never served.
  2. get_wp_cache_key() appends -application/json to the cache key, meaning NR Synthetics requests generate and populate a separate cache bucket, triggering a fresh page build on every synthetic check.

Steps to reproduce

  1. Enable WP Super Cache with Expert/SuperCache mode
  2. Enabled Debug logging
  3. Create a postman request that uses the above accept header
  4. Observe in the debug log: wp_cache_serve_cache_file: visitor does not accept text/html. Not serving cached file. repeating on every synthetic check
  5. Observe cache files being rebuilt on every NR Synthetics request

Expected Bahviour

When text/html is the highest q-value type in the Accept header, WPSC should classify the request as text/html and serve the cached file normally. The function should parse q-values and determine the highest-priority accepted type before comparing against the known JSON types.


Workarounds

The wpsc_accept_headers filter cannot easily fix this because wpsc_get_accept_header() caches its result via static $accept = 'N/A' and is called very early in the PHP request lifecycle — before functions.php is loaded — so any filter hooked from a theme or standard plugin loads too late.

A short-term workaround for those using WP Super Cache in PHP mode (not .htaccess/Expert mode) is to add via an mu-plugin:

<?php
// mu-plugins/fix-nr-accept-header.php
add_filter( 'wpsc_accept_headers', function( $headers ) {
    if ( ! empty( $_SERVER['HTTP_X_NEWRELIC_SYNTHETICS'] ) ) {
        return [];
    }
    return $headers;
} );

However this is NR-specific and does not fix the underlying q-value parsing issue that would affect other monitoring tools (Datadog Synthetics, Pingdom, UptimeRobot, etc.) with similar broad Accept headers.


Suggested Fix

wpsc_get_accept_header() should parse the Accept header properly, sorting types by q-value before comparing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugready-for-agentFully specified, ready for an AFK agent

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions