Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/w3-total-cache/BrowserCache_Plugin.php
+++ b/w3-total-cache/BrowserCache_Plugin.php
@@ -182,13 +182,7 @@
return false;
}
- /**
- * Check User Agent
- */
- $http_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
- if ( stristr( $http_user_agent, W3TC_POWERED_BY ) !== false ) {
- return false;
- }
+ // Do not skip output buffering based on User-Agent (client-spoofable); see Generic_Plugin::can_ob().
return true;
}
--- a/w3-total-cache/Cache_Memcached.php
+++ b/w3-total-cache/Cache_Memcached.php
@@ -244,7 +244,7 @@
/**
* Retrieves multiple cached values in a single request.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $keys Cache keys.
* @param string $group Cache group.
@@ -293,7 +293,7 @@
/**
* Stores multiple values in a single request.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $items Map of cache key => payload.
* @param string $group Cache group.
--- a/w3-total-cache/Cache_Redis.php
+++ b/w3-total-cache/Cache_Redis.php
@@ -426,7 +426,7 @@
/**
* Retrieves multiple cached values in a single request.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $keys Cache keys.
* @param string $group Cache group.
@@ -479,7 +479,7 @@
/**
* Stores multiple values in a single request.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $items Map of cache key => payload.
* @param string $group Cache group.
--- a/w3-total-cache/Cdn_Core.php
+++ b/w3-total-cache/Cdn_Core.php
@@ -960,8 +960,10 @@
break;
case 'cloudflare':
- $is_cdnfsd_authorized = ! empty( $cloudflare_config['email'] ) &&
- ! empty( $cloudflare_config['key'] ) &&
+ $is_cdnfsd_authorized = Extension_CloudFlare_Api::are_api_credentials_usable(
+ isset( $cloudflare_config['email'] ) ? $cloudflare_config['email'] : '',
+ isset( $cloudflare_config['key'] ) ? $cloudflare_config['key'] : ''
+ ) &&
! empty( $cloudflare_config['zone_id'] ) &&
! empty( $cloudflare_config['zone_name'] );
break;
--- a/w3-total-cache/Cdn_Plugin.php
+++ b/w3-total-cache/Cdn_Plugin.php
@@ -110,7 +110,7 @@
*
* @return void
*
- * @since 2.9.3
+ * @since 2.9.4
*/
public function send_headers() {
$cdn_engine = $this->_config->get_string( 'cdn.engine' );
--- a/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php
+++ b/w3-total-cache/DbCache_WpdbInjection_QueryCaching.php
@@ -823,7 +823,7 @@
/**
* Clears request-wide dbcache reject state so subsequent queries can be reconsidered.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
--- a/w3-total-cache/DbCache_WpdbLegacy.php
+++ b/w3-total-cache/DbCache_WpdbLegacy.php
@@ -250,7 +250,7 @@
/**
* Returns the array of dbcache processors attached to this wpdb wrapper.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return array
*/
--- a/w3-total-cache/DbCache_WpdbNew.php
+++ b/w3-total-cache/DbCache_WpdbNew.php
@@ -242,7 +242,7 @@
/**
* Returns the array of dbcache processors attached to this wpdb wrapper.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return array
*/
--- a/w3-total-cache/Extension_CloudFlare_Api.php
+++ b/w3-total-cache/Extension_CloudFlare_Api.php
@@ -55,13 +55,14 @@
/**
* Constructs the Cloudflare API client with the provided configuration.
*
- * @param array $config Configuration array containing 'email', 'key', 'zone_id', and 'timelimit_api_request'.
+ * @param array $config Configuration array containing 'key', 'zone_id', and 'timelimit_api_request'.
+ * 'email' is required only for the legacy Global API key (37 characters); API tokens omit it.
*
* @return void
*/
public function __construct( $config ) {
- $this->_email = $config['email'];
- $this->_key = $config['key'];
+ $this->_email = isset( $config['email'] ) ? trim( (string) $config['email'] ) : '';
+ $this->_key = isset( $config['key'] ) ? trim( (string) $config['key'] ) : '';
$this->_zone_id = ( isset( $config['zone_id'] ) ? $config['zone_id'] : '' );
if ( ! isset( $config['timelimit_api_request'] ) ||
@@ -73,6 +74,49 @@
}
/**
+ * Whether the key string is a legacy Global API Key (37 characters, legacy dashboard format).
+ *
+ * Any other length uses Bearer authentication for API tokens (including historical 40-character
+ * tokens and newer Cloudflare token formats).
+ *
+ * @since 2.9.4
+ *
+ * @param string $key Raw key from settings or request.
+ *
+ * @return bool
+ */
+ public static function is_legacy_global_api_key_string( $key ) {
+ $key = trim( (string) $key );
+
+ return 37 === strlen( $key );
+ }
+
+ /**
+ * Whether email and key are sufficient to authenticate to the Cloudflare v4 API.
+ *
+ * Global API keys require the account email. API tokens only require a non-empty token.
+ *
+ * @since 2.9.4
+ *
+ * @param string $email Account email (may be empty when using an API token).
+ * @param string $key Global API key or API token.
+ *
+ * @return bool
+ */
+ public static function are_api_credentials_usable( $email, $key ) {
+ $key = trim( (string) $key );
+ if ( '' === $key ) {
+ return false;
+ }
+
+ if ( self::is_legacy_global_api_key_string( $key ) ) {
+ return '' !== trim( (string) $email );
+ }
+
+ return true;
+ }
+
+ /**
* Sends an external event notification to Cloudflare.
*
* @param string $type The event type.
@@ -264,7 +308,7 @@
* @throws Exception If authentication is missing or the request fails.
*/
private function _wp_remote_request( $method, $url, $body = array() ) {
- if ( empty( $this->_email ) || empty( $this->_key ) ) {
+ if ( ! $this->_credentials_configured() ) {
throw new Exception( esc_html__( 'Not authenticated.', 'w3-total-cache' ) );
}
@@ -334,7 +378,7 @@
* @throws Exception If authentication is missing or the request fails.
*/
private function _wp_remote_request_with_meta( $method, $url, $body = array() ) {
- if ( empty( $this->_email ) || empty( $this->_key ) ) {
+ if ( ! $this->_credentials_configured() ) {
throw new Exception( esc_html__( 'Not authenticated.', 'w3-total-cache' ) );
}
@@ -404,7 +448,7 @@
* @throws Exception If authentication is missing or the request fails.
*/
private function _wp_remote_request_graphql( $method, $url, $body ) {
- if ( empty( $this->_email ) || empty( $this->_key ) ) {
+ if ( ! $this->_credentials_configured() ) {
throw new Exception( esc_html__( 'Not authenticated.', 'w3-total-cache' ) );
}
@@ -463,6 +507,33 @@
}
/**
+ * Whether the stored key matches the legacy Global API Key format (X-Auth-Key + X-Auth-Email).
+ *
+ * Cloudflare Global API keys are 37 characters. API tokens use Bearer auth and are opaque strings
+ * of varying lengths (including historical 40-character tokens and newer formats).
+ *
+ * @since 2.9.4
+ *
+ * @return bool
+ */
+ private function _is_legacy_global_api_key() {
+ return self::is_legacy_global_api_key_string( $this->_key );
+ }
+
+ /**
+ * Whether credentials are sufficient for the detected auth mode.
+ *
+ * API tokens require only a non-empty key. Legacy global keys also require the account email.
+ *
+ * @since 2.9.4
+ *
+ * @return bool
+ */
+ private function _credentials_configured() {
+ return self::are_api_credentials_usable( $this->_email, $this->_key );
+ }
+
+ /**
* Generates HTTP request headers for Cloudflare API requests.
*
* @return array The headers array for API requests.
@@ -470,27 +541,26 @@
* @throws Exception If the authentication credentials are invalid or missing.
*/
private function _generate_wp_remote_request_headers() {
- if ( empty( $this->_email ) || empty( $this->_key ) ) {
- throw new Exception( esc_html__( 'Missing authentication email and/or API token / global key.', 'w3-total-cache' ) );
+ if ( ! $this->_credentials_configured() ) {
+ throw new Exception(
+ esc_html__(
+ 'Missing API token, or Global API key with account email.',
+ 'w3-total-cache'
+ )
+ );
}
- $headers = array();
-
- if ( 40 === strlen( $this->_key ) ) { // CF API Token.
- $headers = array(
- 'Content-Type' => 'application/json',
- 'Authorization' => 'Bearer ' . $this->_key,
- );
- } elseif ( 37 === strlen( $this->_key ) ) { // CF Legacy API Global Key.
- $headers = array(
+ if ( $this->_is_legacy_global_api_key() ) {
+ return array(
'Content-Type' => 'application/json',
'X-Auth-Key' => $this->_key,
'X-Auth-Email' => $this->_email,
);
- } else {
- throw new Exception( esc_html__( 'Improper API token / global key length.', 'w3-total-cache' ) );
}
- return $headers;
+ return array(
+ 'Content-Type' => 'application/json',
+ 'Authorization' => 'Bearer ' . $this->_key,
+ );
}
}
--- a/w3-total-cache/Extension_CloudFlare_Page.php
+++ b/w3-total-cache/Extension_CloudFlare_Page.php
@@ -63,7 +63,7 @@
$key = $c->get_string( array( 'cloudflare', 'key' ) );
$zone_id = $c->get_string( array( 'cloudflare', 'zone_id' ) );
- if ( empty( $email ) || empty( $key ) || empty( $zone_id ) ) {
+ if ( empty( $zone_id ) || ! Extension_CloudFlare_Api::are_api_credentials_usable( $email, $key ) ) {
$state = 'not_configured';
} else {
$settings = array();
--- a/w3-total-cache/Extension_CloudFlare_Plugin_Admin.php
+++ b/w3-total-cache/Extension_CloudFlare_Plugin_Admin.php
@@ -205,8 +205,10 @@
public function w3tc_admin_bar_menu( $menu_items ) {
if (
$this->_config->get_boolean( array( 'cloudflare', 'enabled' ) ) &&
- ! empty( $this->_config->get_string( array( 'cloudflare', 'email' ) ) ) &&
- ! empty( $this->_config->get_string( array( 'cloudflare', 'key' ) ) ) &&
+ Extension_CloudFlare_Api::are_api_credentials_usable(
+ $this->_config->get_string( array( 'cloudflare', 'email' ) ),
+ $this->_config->get_string( array( 'cloudflare', 'key' ) )
+ ) &&
! empty( $this->_config->get_string( array( 'cloudflare', 'zone_id' ) ) )
) {
$current_page = Util_Request::get_string( 'page', 'w3tc_dashboard' );
@@ -279,42 +281,53 @@
$action = Util_Request::get_string( 'command' );
$value = Util_Request::get_string( 'value' );
$nonce = Util_Request::get_string( '_wpnonce' );
+ $error = null;
if ( ! wp_verify_nonce( $nonce, 'w3tc' ) ) {
$error = 'Access denied.';
- } elseif ( ! $email ) {
- $error = 'Empty email.';
- } elseif ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
- $error = 'Invalid email.';
} elseif ( ! $key ) {
$error = 'Empty token / global key.';
- } elseif ( ! $zone ) {
- $error = 'Empty zone.';
- } elseif ( strpos( $zone, '.' ) === false ) {
- $error = 'Invalid domain.';
- } elseif ( ! in_array( $action, $actions, true ) ) {
- $error = 'Invalid action.';
- } else {
- $config = array(
- 'email' => $email,
- 'key' => $key,
- 'zone' => $zone,
- );
-
- @$this->api = new Extension_CloudFlare_Api( $config );
-
- @set_time_limit( $this->_config->get_integer( array( 'cloudflare', 'timelimit.api_request' ) ) ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
- $response = $this->api->api_request( $action, $value );
+ } elseif ( Extension_CloudFlare_Api::is_legacy_global_api_key_string( $key ) ) {
+ if ( ! $email ) {
+ $error = 'Empty email.';
+ } elseif ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ $error = 'Invalid email.';
+ }
+ } elseif ( $email && ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
+ $error = 'Invalid email.';
+ } elseif ( ! Extension_CloudFlare_Api::are_api_credentials_usable( $email, $key ) ) {
+ $error = 'Invalid authentication (API token, or Global API key with account email).';
+ }
- if ( $response ) {
- if ( 'success' === $response->result ) {
- $result = true;
- $error = 'OK';
+ if ( null === $error ) {
+ if ( ! $zone ) {
+ $error = 'Empty zone.';
+ } elseif ( strpos( $zone, '.' ) === false ) {
+ $error = 'Invalid domain.';
+ } elseif ( ! in_array( $action, $actions, true ) ) {
+ $error = 'Invalid action.';
+ } else {
+ $config = array(
+ 'email' => $email,
+ 'key' => $key,
+ 'zone' => $zone,
+ );
+
+ @$this->api = new Extension_CloudFlare_Api( $config );
+
+ @set_time_limit( $this->_config->get_integer( array( 'cloudflare', 'timelimit.api_request' ) ) ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
+ $response = $this->api->api_request( $action, $value );
+
+ if ( $response ) {
+ if ( 'success' === $response->result ) {
+ $result = true;
+ $error = 'OK';
+ } else {
+ $error = $response->msg;
+ }
} else {
- $error = $response->msg;
+ $error = 'Unable to make Cloudflare API request.';
}
- } else {
- $error = 'Unable to make Cloudflare API request.';
}
}
@@ -365,7 +378,7 @@
$email = $this->_config->get_string( array( 'cloudflare', 'email' ) );
$key = $this->_config->get_string( array( 'cloudflare', 'key' ) );
- if ( empty( $email ) || empty( $key ) ) {
+ if ( ! Extension_CloudFlare_Api::are_api_credentials_usable( $email, $key ) ) {
return $actions;
}
--- a/w3-total-cache/Extension_ImageService_Environment.php
+++ b/w3-total-cache/Extension_ImageService_Environment.php
@@ -150,7 +150,7 @@
/**
* Generate AVIF rewrite rules (higher priority).
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @see Dispatcher::nginx_rules_for_browsercache_section()
*
@@ -266,7 +266,7 @@
/**
* Generate WebP rewrite rules (lower priority than AVIF).
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @see Dispatcher::nginx_rules_for_browsercache_section()
*
--- a/w3-total-cache/Generic_Plugin.php
+++ b/w3-total-cache/Generic_Plugin.php
@@ -45,31 +45,6 @@
private $frontend_notice;
/**
- * Output buffer nesting level recorded at ob_start() time.
- *
- * Used by ob_shutdown() to identify which buffer level belongs to W3TC.
- *
- * @since 2.9.2
- *
- * @var int
- */
- private $_ob_level = 0;
-
- /**
- * Guards against ob_shutdown() being invoked twice.
- *
- * Function `ob_shutdown()` is registered both as a WordPress 'shutdown' action
- * (priority 0, so it runs before wp_ob_end_flush_all at priority 1) and as
- * a PHP shutdown function (fallback for abnormal termination). This flag
- * ensures the second invocation is a no-op.
- *
- * @since 2.9.2
- *
- * @var bool
- */
- private $_ob_shutdown_done = false;
-
- /**
* Constructor
*
* @return void
@@ -124,32 +99,10 @@
}
if ( $this->can_ob() ) {
- add_filter( 'wp_die_ajax_handler', array( $this, 'wp_die_handler' ) );
- add_filter( 'wp_die_json_handler', array( $this, 'wp_die_handler' ) );
add_filter( 'wp_die_xml_handler', array( $this, 'wp_die_handler' ) );
add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
- /*
- * Register with no callback so no display-handler restriction applies.
- * ob_callback() is invoked explicitly in ob_shutdown() instead.
- * PHP's display-handler restriction (ob_start() forbidden inside a
- * callback) made every ob flush mechanism unsafe for mfunc processing.
- */
- ob_start();
- $this->_ob_level = ob_get_level();
-
- /*
- * Hook into WordPress 'shutdown' at priority 0 so ob_shutdown runs
- * before wp_ob_end_flush_all (priority 1). ob_shutdown processes the
- * buffer, places the result in a fresh ob_start() buffer, and removes
- * wp_ob_end_flush_all so it cannot flush that buffer prematurely.
- * PHP auto-flushes all open buffers at true end-of-script, after every
- * register_shutdown_function callback has run.
- * The PHP shutdown function below is a fallback for abnormal
- * termination paths where the WordPress 'shutdown' action may not fire.
- */
- add_action( 'shutdown', array( $this, 'ob_shutdown' ), 0 );
- register_shutdown_function( array( $this, 'ob_shutdown' ) );
+ ob_start( array( $this, 'ob_callback' ) );
}
$this->register_plugin_check_filters();
@@ -608,8 +561,10 @@
if (
$this->_config->get_boolean( 'cdnfsd.enabled' ) &&
'cloudflare' === $this->_config->get_string( 'cdnfsd.engine' ) &&
- ! empty( $this->_config->get_string( array( 'cloudflare', 'email' ) ) ) &&
- ! empty( $this->_config->get_string( array( 'cloudflare', 'key' ) ) ) &&
+ Extension_CloudFlare_Api::are_api_credentials_usable(
+ $this->_config->get_string( array( 'cloudflare', 'email' ) ),
+ $this->_config->get_string( array( 'cloudflare', 'key' ) )
+ ) &&
! empty( $this->_config->get_string( array( 'cloudflare', 'zone_id' ) ) ) &&
in_array( 'cloudflare', array_keys( Extensions_Util::get_active_extensions( $this->_config ) ), true ) &&
(
@@ -1054,178 +1009,10 @@
return false;
}
- /**
- * Check User Agent
- */
- $http_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
- if ( stristr( $http_user_agent, W3TC_POWERED_BY ) !== false ) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Processes and outputs the W3TC output buffer at shutdown time.
- *
- * Registered both as a WordPress 'shutdown' action (priority 0) and as a
- * PHP shutdown function (fallback for abnormal termination). The flag
- * $_ob_shutdown_done prevents double-invocation.
- *
- * ob_callback() is invoked explicitly here rather than as a PHP display
- * handler, because display handlers (callbacks invoked during ob_end_clean,
- * ob_end_flush, or PHP's own buffer cleanup) set PHP's internal ob_lock
- * flag, which forbids any nested ob_start() call. _parse_dynamic_mfunc()
- * requires ob_start() to capture eval() output, so it must not run inside
- * a display handler. The ob_start() in run() is registered with no callback
- * to ensure no display handler is ever registered for our buffer.
- *
- * After processing the buffer, output is placed into a fresh ob_start()
- * buffer rather than echoed directly. wp_ob_end_flush_all (priority 1) is
- * removed so it cannot flush that buffer early. PHP then auto-flushes all
- * open output buffers after every shutdown function has run, which keeps
- * response headers open for any remaining shutdown handlers (e.g. late
- * setcookie() or header() calls from other plugins or session libraries).
- * The same removal runs on the early-return path when our buffer was
- * already closed, so Core does not flush at priority 1 before those hooks.
- *
- * @since 2.9.2
- *
- * @return void
- */
- public function ob_shutdown() {
- if ( $this->_ob_shutdown_done ) {
- return;
- }
-
- $this->_ob_shutdown_done = true;
-
- /*
- * For non-HTML response types (e.g. application/json from AJAX plugins like
- * FacetWP that do not define DOING_AJAX), discard nested output buffer
- * contents rather than flushing them down into ours. Nested OBs opened by
- * themes or plugins during the request may contain partial page HTML that
- * must not be prepended to a JSON response body.
- *
- * For standard HTML responses, flush nested OBs so their content reaches
- * W3TC's buffer and is processed (minified, CDN-rewritten, cached, etc.).
- */
- $is_html = $this->_is_html_response();
-
- /*
- * Flush (or discard) any nested output buffers added by WordPress or other
- * plugins, so their content is included in (or excluded from) ours.
- */
- while ( ob_get_level() > $this->_ob_level ) {
- if ( $is_html ) {
- ob_end_flush();
- } else {
- ob_end_clean();
- }
- }
-
- if ( ob_get_level() < $this->_ob_level ) {
- /*
- * Our buffer was already closed (e.g. by a redirect or wp_die). Still
- * remove wp_ob_end_flush_all so shutdown priority 1 does not flush
- * remaining buffers before later hooks (e.g. setcookie at priority 999).
- * When headers are already committed, late header() calls still fail
- * harmlessly; when output remains buffered elsewhere, this preserves
- * compatibility with session/cookie finalization on shutdown.
- */
- remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
- return;
- }
-
- /*
- * Read the buffer and close it. Because ob_start() was registered with
- * no callback, ob_get_clean() never triggers a display handler, so
- * ob_start() calls inside ob_callback() (e.g. _parse_dynamic_mfunc)
- * are permitted.
- */
- $buffer = (string) ob_get_clean();
-
- /*
- * Run the W3TC processing pipeline for HTML responses and for WordPress
- * REST API requests (where REST_REQUEST is defined). For other non-HTML
- * responses (e.g. FacetWP or other AJAX endpoints that return JSON
- * containing HTML snippets), skip ob_callback() entirely to avoid
- * running lazy load or other HTML processors over the payload.
- *
- * REST API responses need ob_callback() so the pagecache callback can
- * write them to cache when pgcache.rest is set to 'cache'. Other
- * ob_callback() processors (minify, CDN, lazyload, etc.) are safe for
- * REST JSON because can_print_comment() / is_html_xml() return false,
- * preventing HTML-only modifications from being applied.
- */
- if ( $is_html || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
- $buffer = $this->ob_callback( $buffer );
- }
-
- /*
- * Place the processed output into a new, callback-free buffer instead of
- * echoing it directly. This keeps response headers open so that any
- * remaining shutdown handlers at higher priorities — or registered PHP
- * shutdown functions — can still call setcookie() / header() without
- * triggering a "headers already sent" warning.
- *
- * Removing wp_ob_end_flush_all (shutdown priority 1) prevents a premature
- * flush of this buffer during the WordPress shutdown action. PHP
- * automatically flushes all open output buffers at true end-of-script,
- * after every register_shutdown_function callback has completed.
- */
- ob_start();
- echo $buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
- }
-
- /**
- * Determines whether the current HTTP response is an HTML (or XML) content type.
- *
- * Used by ob_shutdown() to decide whether to flush or discard nested output
- * buffers. For HTML responses, nested OB content (from themes/plugins) should
- * be included in W3TC's buffer. For non-HTML responses (e.g. application/json
- * from AJAX plugins), nested OB content should be discarded so it cannot
- * contaminate the response.
- *
- * When no Content-Type header has been sent yet the response defaults to HTML,
- * so true is returned.
- *
- * @since 2.9.3
- *
- * @return bool True if the response is HTML/XML, false otherwise.
- */
- protected function _is_html_response() {
- $html_types = array(
- 'text/html',
- 'text/xml',
- 'text/xsl',
- 'application/xhtml+xml',
- 'application/rss+xml',
- 'application/atom+xml',
- 'application/rdf+xml',
- 'application/xml',
- );
-
- foreach ( headers_list() as $header ) {
- $header = strtolower( $header );
- if ( strpos( $header, 'content-type:' ) === false ) {
- continue;
- }
-
- foreach ( $html_types as $type ) {
- if ( strpos( $header, $type ) !== false ) {
- return true;
- }
- }
-
- // A Content-Type header is present but it is not one of the HTML/XML
- // types above (e.g. application/json, text/plain). Treat as non-HTML.
- return false;
- }
+ // Do not skip output buffering based on User-Agent: the value is client-controlled.
+ // A request claiming "W3 Total Cache" would previously bypass ob_callback, skipping
+ // page-cache processing and leaking W3TC_DYNAMIC_SECURITY from unprocessed mfunc/mclude.
- // No Content-Type header sent yet — default to treating as HTML so that
- // standard WordPress page requests continue to work correctly.
return true;
}
--- a/w3-total-cache/Licensing_Plugin_Admin.php
+++ b/w3-total-cache/Licensing_Plugin_Admin.php
@@ -36,7 +36,7 @@
/**
* Usermeta key for storing dismissed license notices.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @var string
*/
@@ -46,7 +46,7 @@
* Time in seconds after which a dismissed notice can reappear if conditions persist.
* Set to 6 days (automatic license check interval is 5 days).
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @var int
*/
@@ -58,7 +58,7 @@
* The HLT (Hosted Login Token) in the billing URL is only valid for 19 minutes,
* so we cache for 19 minutes to ensure users always get a valid token with some buffer.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @var int
*/
@@ -392,7 +392,7 @@
* This is called on non-W3TC admin pages where the lightbox isn't already loaded.
* Only enqueues if there's a license status that would display a notice.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -417,7 +417,7 @@
/**
* Enqueues lightbox JavaScript and CSS assets.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -459,7 +459,7 @@
* (lowercase letters, numbers, hyphens, underscores) and replaces
* dots with hyphens for readability.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $status The license status to sanitize.
*
@@ -559,7 +559,7 @@
* Called on admin_enqueue_scripts to register the script early,
* which can then be enqueued later when notices are displayed.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -616,7 +616,7 @@
/**
* Generates a license notice based on the provided license status and key.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $status The current status of the license (e.g., 'active', 'expired').
* @param string $license_key The license key associated with the plugin.
@@ -746,7 +746,7 @@
* Uses transient caching to avoid making HTTP requests on every page load.
* The URL is cached for 1 hour to balance freshness with performance.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $license_key The license key used to generate the billing URL.
*
@@ -779,7 +779,7 @@
/**
* Fetches the billing URL from the API.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $license_key The license key used to generate the billing URL.
*
@@ -998,7 +998,7 @@
*
* Saves the dismissal timestamp in usermeta for persistent per-user dismissal.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -1034,7 +1034,7 @@
*
* Forces an immediate license check and clears cached billing URL.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -1068,7 +1068,7 @@
* If the reset time has elapsed and the condition still persists, the dismissal
* is cleared and the notice will show again.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $notice_id The unique identifier for the notice.
*
@@ -1102,7 +1102,7 @@
* Uses a targeted query to only retrieve users who have the specific notice dismissed,
* rather than all users with any dismissed notices.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $notice_id The unique identifier for the notice to clear.
*
--- a/w3-total-cache/ObjectCache_WpObjectCache_Regular.php
+++ b/w3-total-cache/ObjectCache_WpObjectCache_Regular.php
@@ -1194,7 +1194,7 @@
/**
* Persist multiple cache entries using the most efficient method available.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param object $cache Cache engine instance.
* @param array $payload Map of cache_key => structured payload.
--- a/w3-total-cache/PageSpeed_Data.php
+++ b/w3-total-cache/PageSpeed_Data.php
@@ -65,7 +65,7 @@
/**
* Collect core web vital metrics in a consistent format.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $data PageSpeed data payload.
* @param string $metric Lighthouse audit identifier.
@@ -83,7 +83,7 @@
/**
* Log the raw metric keys and configured instruction keys when debugging is enabled.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $pagespeed_data Prepared PageSpeed data.
*
@@ -121,7 +121,7 @@
/**
* Collect audits belonging to the given Lighthouse category group.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $data Raw Lighthouse API payload.
* @param string $group Lighthouse category group identifier.
@@ -157,7 +157,7 @@
/**
* Format a single Lighthouse audit into the structure expected by the UI.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $audit_id Lighthouse audit identifier.
* @param array $audit Lighthouse audit payload.
@@ -191,7 +191,7 @@
/**
* Normalize Lighthouse audit details to a list structure.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param mixed $details Lighthouse audit details.
*
@@ -219,7 +219,7 @@
/**
* Determine which Core Web Vitals an audit influences.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $audit_id Lighthouse audit identifier.
* @param array $audit Lighthouse audit payload.
@@ -248,7 +248,7 @@
/**
* Normalize the network dependency tree insight payload.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $details Lighthouse network dependency tree details payload.
*
@@ -283,7 +283,7 @@
/**
* Normalize a network dependency chain node recursively.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $node Node payload.
*
@@ -310,7 +310,7 @@
/**
* Normalize preconnect insight sections.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $section Section payload from Lighthouse.
*
@@ -360,7 +360,7 @@
/**
* Provide a mapping of audit identifiers to Core Web Vital type tags.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return array
*/
@@ -409,7 +409,7 @@
/**
* Normalize score values to 0-100 scale while avoiding PHP warnings when score is missing.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param mixed $score Score from the Lighthouse payload.
*
@@ -426,7 +426,7 @@
/**
* Drop metrics that Google didn't include in the latest payload.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $metrics Raw metrics bucket.
*
@@ -448,7 +448,7 @@
/**
* Attach instructions for metrics that survived the filtering step.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $pagespeed_data Prepared PageSpeed data.
*
--- a/w3-total-cache/PgCache_ContentGrabber.php
+++ b/w3-total-cache/PgCache_ContentGrabber.php
@@ -110,6 +110,13 @@
private $_page_key_extension;
/**
+ * Shutdown buffer
+ *
+ * @var string
+ */
+ private $_shutdown_buffer = '';
+
+ /**
* Cache reject reason
*
* @var string
@@ -579,20 +586,41 @@
}
}
+ // We can't capture output in ob_callback so we use shutdown function.
if ( $has_dynamic ) {
- $compression = $this->_page_key_extension['compression'];
- if ( defined( 'W3TC_PAGECACHE_OUTPUT_COMPRESSION_OFF' ) ) {
- $compression = false;
- }
+ $this->_shutdown_buffer = $buffer;
+
+ $buffer = '';
- $buffer = $this->_parse_dynamic( $buffer );
- $buffer = $this->_compress( $buffer, $compression );
+ register_shutdown_function(
+ array(
+ $this,
+ 'shutdown',
+ )
+ );
}
return $buffer;
}
/**
+ * Handles the shutdown process for compressing and outputting the page buffer.
+ *
+ * @return void
+ */
+ public function shutdown() {
+ $compression = $this->_page_key_extension['compression'];
+
+ // Parse dynamic content.
+ $buffer = $this->_parse_dynamic( $this->_shutdown_buffer );
+
+ // Compress page according to headers already set.
+ $compressed_buffer = $this->_compress( $buffer, $compression );
+
+ echo $compressed_buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ /**
* Determines if the cache can be read for the current request.
*
* @return bool True if cache can be read, false otherwise.
@@ -2332,7 +2360,7 @@
$compressions_to_store = array( false );
}
- // For dynamic pages, return the unprocessed buffer so ob_callback can parse and compress it directly.
+ // Right now dont return compressed buffer if we are dynamic that will happen on shutdown after processing dynamic stuff.
$compression_of_returned_content = ( $has_dynamic ? false : $compression_header );
$headers = $this->_get_cached_headers( $response_headers['plain'] );
--- a/w3-total-cache/SetupGuide_Plugin_Admin.php
+++ b/w3-total-cache/SetupGuide_Plugin_Admin.php
@@ -924,7 +924,7 @@
/**
* Build the SQL statements used for database cache benchmarking.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param wpdb $wpdb WordPress database object.
*
@@ -944,7 +944,7 @@
/**
* Execute a repeatable set of read-heavy queries to measure cache performance.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param wpdb $wpdb WordPress database object.
* @param array $queries List of SQL queries to execute.
@@ -967,7 +967,7 @@
/**
* Clear runtime caches to reduce contamination between timed runs.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -982,7 +982,7 @@
/**
* Clear dbcache reject state for the current request so tests can evaluate with fresh context.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return void
*/
@@ -1015,7 +1015,7 @@
/**
* Collect a set of post IDs to be used when benchmarking the object cache.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return array
*/
@@ -1047,7 +1047,7 @@
/**
* Run a representative workload that should benefit from a persistent object cache.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $post_ids IDs to request in the query.
* @param string $payload_key Cache key used to check persistence.
@@ -1095,7 +1095,7 @@
/**
* Clear runtime object cache data while preserving persistent stores when possible.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @return string Which flush method was used.
*/
--- a/w3-total-cache/Util_PageSpeed.php
+++ b/w3-total-cache/Util_PageSpeed.php
@@ -670,7 +670,7 @@
/**
* Render the specialized Network Dependency Tree insight.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $insight Insight payload.
* @param string $notice_class Notice classes.
@@ -736,7 +736,7 @@
/**
* Render the network chain list recursively.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $chains Chain list.
*
@@ -759,7 +759,7 @@
/**
* Render a single network chain node.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $node Node payload.
*
@@ -818,7 +818,7 @@
/**
* Render preconnect sections.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $section Section payload.
* @param string $default_name Fallback title.
@@ -856,7 +856,7 @@
/**
* Render a nested table cell from headings + sub-items.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $headings Table headings.
* @param array $rows Table rows.
@@ -899,7 +899,7 @@
/**
* Format a subitem value according to the heading definition.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $heading Heading definition.
* @param array $row Row data.
@@ -949,7 +949,7 @@
/**
* Format a source-location value into HTML.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param array $source Source location payload.
*
@@ -979,7 +979,7 @@
/**
* Append the document latency status icon for qualifying rows.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param string $audit_id Audit identifier.
* @param array $item Detail item.
@@ -1016,7 +1016,7 @@
/**
* Format bytes into readable strings.
*
- * @since 2.9.3
+ * @since 2.9.4
*
* @param int $bytes Byte value.
*
--- a/w3-total-cache/vendor/composer/installed.php
+++ b/w3-total-cache/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'boldgrid/w3-total-cache',
- 'pretty_version' => '2.9.3',
- 'version' => '2.9.3.0',
- 'reference' => '202309df72166993e350488695deaa0bc39b97e6',
+ 'pretty_version' => '2.9.4',
+ 'version' => '2.9.4.0',
+ 'reference' => 'ca70153e80a9cf5dcff16743596abe5253d227b9',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -38,9 +38,9 @@
'dev_requirement' => false,
),
'boldgrid/w3-total-cache' => array(
- 'pretty_version' => '2.9.3',
- 'version' => '2.9.3.0',
- 'reference' => '202309df72166993e350488695deaa0bc39b97e6',
+ 'pretty_version' => '2.9.4',
+ 'version' => '2.9.4.0',
+ 'reference' => 'ca70153e80a9cf5dcff16743596abe5253d227b9',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -65,9 +65,9 @@
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
- 'pretty_version' => '2.8.0',
- 'version' => '2.8.0.0',
- 'reference' => '21dc724a0583619cd1652f673303492272778051',
+ 'pretty_version' => '2.9.0',
+ 'version' => '2.9.0.0',
+ 'reference' => '7d0ed42f28e42d61352a7a79de682e5e67fec884',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
@@ -164,9 +164,9 @@
'dev_requirement' => false,
),
'sebastian/comparator' => array(
- 'pretty_version' => '3.0.6',
- 'version' => '3.0.6.0',
- 'reference' => '4b3c947888c81708b20fb081bb653a2ba68f989a',
+ 'pretty_version' => '3.0.7',
+ 'version' => '3.0.7.0',
+ 'reference' => 'bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/comparator',
'aliases' => array(),
--- a/w3-total-cache/vendor/guzzlehttp/psr7/src/LimitStream.php
+++ b/w3-total-cache/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -63,9 +63,9 @@
return null;
} elseif ($this->limit === -1) {
return $length - $this->offset;
- } else {
- return min($this->limit, $length - $this->offset);
}
+
+ return min($this->limit, $length - $this->offset);
}
/**
--- a/w3-total-cache/vendor/guzzlehttp/psr7/src/MessageTrait.php
+++ b/w3-total-cache/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -29,6 +29,9 @@
return $this->protocol;
}
+ /**
+ * @return static
+ */
public function withProtocolVersion($version): MessageInterface
{
if ($this->protocol === $version) {
@@ -69,6 +72,9 @@
return implode(', ', $this->getHeader($header));
}
+ /**
+ * @return static
+ */
public function withHeader($header, $value): MessageInterface
{
$this->assertHeader($header);
@@ -85,6 +91,9 @@
return $new;
}
+ /**
+ * @return static
+ */
public function withAddedHeader($header, $value): MessageInterface
{
$this->assertHeader($header);
@@ -103,6 +112,9 @@
return $new;
}
+ /**
+ * @return static
+ */
public function withoutHeader($header): MessageInterface
{
$normalized = strtolower($header);
@@ -128,6 +140,9 @@
return $this->stream;
}
+ /**
+ * @return static
+ */
public function withBody(StreamInterface $body): MessageInterface
{
if ($body === $this->stream) {
--- a/w3-total-cache/vendor/guzzlehttp/psr7/src/MimeType.php
+++ b/w3-total-cache/vendor/guzzlehttp/psr7/src/MimeType.php
@@ -7,22 +7,23 @@
final class MimeType
{
private const MIME_TYPES = [
+ '123' => 'application/vnd.lotus-1-2-3',
'1km' => 'application/vnd.1000minds.decision-model+xml',
+ '210' => 'model/step',
'3dml' => 'text/vnd.in3d.3dml',
'3ds' => 'image/x-3ds',
'3g2' => 'video/3gpp2',
- '3gp' => 'video/3gp',
+ '3gp' => 'video/3gpp',
'3gpp' => 'video/3gpp',
'3mf' => 'model/3mf',
'7z' => 'application/x-7z-compressed',
'7zip' => 'application/x-7z-compressed',
- '123' => 'application/vnd.lotus-1-2-3',
'aab' => 'application/x-authorware-bin',
'aac' => 'audio/aac',
'aam' => 'application/x-authorware-map',
'aas' => 'application/x-authorware-seg',
'abw' => 'application/x-abiword',
- 'ac' => 'application/vnd.nokia.n-gage.ac+xml',
+ 'ac' => 'application/pkix-attr-cert',
'ac3' => 'audio/ac3',
'acc' => 'application/vnd.americandynamics.acc',
'ace' => 'application/x-ace-compressed',
@@ -35,7 +36,7 @@
'afp' => 'application/vnd.ibm.modcap',
'age' => 'application/vnd.age',
'ahead' => 'application/vnd.ahead.space',
- 'ai' => 'application/pdf',
+ 'ai' => 'application/postscript',
'aif' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
@@ -55,7 +56,7 @@
'apr' => 'application/vnd.lotus-approach',
'arc' => 'application/x-freearc',
'arj' => 'application/x-arj',
- 'asc' => 'application/pgp-signature',
+ 'asc' => 'application/pgp-keys',
'asf' => 'video/x-ms-asf',
'asm' => 'text/x-asm',
'aso' => 'application/vnd.accpac.simply.aso',
@@ -66,7 +67,7 @@
'atomdeleted' => 'application/atomdeleted+xml',
'atomsvc' => 'application/atomsvc+xml',
'atx' => 'application/vnd.antix.game-component',
- 'au' => 'audio/x-au',
+ 'au' => 'audio/basic',
'avci' => 'image/avci',
'avcs' => 'image/avcs',
'avi' => 'video/x-msvideo',
@@ -77,15 +78,18 @@
'azv' => 'image/vnd.airzip.accelerator.azv',
'azw' => 'application/vnd.amazon.ebook',
'b16' => 'image/vnd.pco.b16',
+ 'bary' => 'model/vnd.bary',
'bat' => 'application/x-msdownload',
'bcpio' => 'application/x-bcpio',
'bdf' => 'application/x-font-bdf',
'bdm' => 'application/vnd.syncml.dm+wbxml',
- 'bdoc' => 'application/x-bdoc',
+ 'bdo' => 'application/vnd.nato.bindingdataobject+xml',
+ 'bdoc' => 'application/bdoc',
'bed' => 'application/vnd.realvnc.bed',
'bh2' => 'application/vnd.fujitsu.oasysprs',
'bin' => 'application/octet-stream',
'blb' => 'application/x-blorb',
+ 'blend' => 'application/x-blender',
'blorb' => 'application/x-blorb',
'bmi' => 'application/vnd.bmi',
'bmml' => 'application/vnd.balsamiq.bmml+xml',
@@ -95,6 +99,8 @@
'boz' => 'application/x-bzip2',
'bpk' => 'application/octet-stream',
'bpmn' => 'application/octet-stream',
+ 'brush' => 'application/vnd.procreate.brush',
+ 'brushset' => 'application/vnd.procreate.brushset',
'bsp' => 'model/vnd.valve.source.compiled-map',
'btf' => 'image/prs.btif',
'btif' => 'image/prs.btif',
@@ -102,13 +108,13 @@
'bz' => 'application/x-bzip',
'bz2' => 'application/x-bzip2',
'c' => 'text/x-c',
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
'c4d' => 'application/vnd.clonk.c4group',
'c4f' => 'application/vnd.clonk.c4group',
'c4g' => 'application/vnd.clonk.c4group',
'c4p' => 'application/vnd.clonk.c4group',
'c4u' => 'application/vnd.clonk.c4group',
- 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
- 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
'cab' => 'application/vnd.ms-cab-compressed',
'caf' => 'audio/x-caf',
'cap' => 'application/vnd.tcpdump.pcap',
@@ -132,7 +138,6 @@
'cdmid' => 'application/cdmi-domain',
'cdmio' => 'application/cdmi-object',
'cdmiq' => 'application/cdmi-queue',
- 'cdr' => 'application/cdr',
'cdx' => 'chemical/x-cdx',
'cdxml' => 'application/vnd.chemdraw+xml',
'cdy' => 'application/vnd.cinderella',
@@ -147,7 +152,7 @@
'cil' => 'application/vnd.ms-artgalry',
'cjs' => 'application/node',
'cla' => 'application/vnd.claymore',
- 'class' => 'application/octet-stream',
+ 'class' => 'application/java-vm',
'cld' => 'model/vnd.cld',
'clkk' => 'application/vnd.crick.clicker.keyboard',
'clkp' => 'application/vnd.crick.clicker.palette',
@@ -194,6 +199,8 @@
'davmount' => 'application/davmount+xml',
'dbf' => 'application/vnd.dbf',
'dbk' => 'application/docbook+xml',
+ 'dcm' => 'application/dicom',
+ 'dcmp' => 'application/vnd.dcmp+xml',
'dcr' => 'application/x-director',
'dcurl' => 'text/vnd.curl.dcurl',
'dd2' => 'application/vnd.oma.dd2+xml',
@@ -221,19 +228,22 @@
'dmp' => 'application/vnd.tcpdump.pcap',
'dms' => 'application/octet-stream',
'dna' => 'application/vnd.dna',
+ 'dng' => 'image/x-adobe-dng',
'doc' => 'application/msword',
- 'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'docm' => 'application/vnd.ms-word.document.macroenabled.12',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dot' => 'application/msword',
- 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'dp' => 'application/vnd.osgi.dp',
'dpg' => 'application/vnd.dpgraph',
'dpx' => 'image/dpx',
'dra' => 'audio/vnd.dra',
'drle' => 'image/dicom-rle',
+ 'drm' => 'application/vnd.procreate.dream',
'dsc' => 'text/prs.lines.tag',
'dssc' => 'application/dssc+der',
+ 'dst' => 'application/octet-stream',
'dtb' => 'application/x-dtbook+xml',
'dtd' => 'application/xml-dtd',
'dts' => 'audio/vnd.dts',
@@ -285,10 +295,12 @@
'f4v' => 'video/mp4',
'f77' => 'text/x-fortran',
'f90' => 'text/x-fortran',
+ 'facti' => 'image/vnd.blockfact.facti',
'fbs' => 'image/vnd.fastbidsheet',
+ 'fbx' => 'application/vnd.autodesk.fbx',
'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
'fcs' => 'application/vnd.isac.fcs',
- 'fdf' => 'application/vnd.fdf',
+ 'fdf' => 'application/fdf',
'fdt' => 'application/fdt+xml',
'fe_launch' => 'application/vnd.denovo.fcselayout-link',
'fg5' => 'application/vnd.fujitsu.oasysgp',
@@ -330,21 +342,25 @@
'gca' => 'application/x-gca-compressed',
'gdl' => 'model/vnd.gdl',
'gdoc' => 'application/vnd.google-apps.document',
+ 'gdraw' => 'application/vnd.google-apps.drawing',
'ged' => 'text/vnd.familysearch.gedcom',
'geo' => 'application/vnd.dynageo',
'geojson' => 'application/geo+json',
'gex' => 'application/vnd.geometry-explorer',
+ 'gform' => 'application/vnd.google-apps.form',
'ggb' => 'application/vnd.geogebra.file',
+ 'ggs' => 'application/vnd.geogebra.slides',
'ggt' => 'application/vnd.geogebra.tool',
'ghf' => 'application/vnd.groove-help',
'gif' => 'image/gif',
'gim' => 'application/vnd.groove-identity-message',
+ 'gjam' => 'application/vnd.google-apps.jam',
'glb' => 'model/gltf-binary',
'gltf' => 'model/gltf+json',
+ 'gmap' => 'application/vnd.google-apps.map',
'gml' => 'application/gml+xml',
'gmx' => 'application/vnd.gmx',
'gnumeric' => 'application/x-gnumeric',
- 'gpg' => 'application/gpg-keys',
'gph' => 'application/vnd.flographit',
'gpx' => 'application/gpx+xml',
'gqf' => 'application/vnd.grafeq',
@@ -354,8 +370,10 @@
'gre' => 'application/vnd.geometry-explorer',
'grv' => 'application/vnd.groove-injector',
'grxml' => 'application/srgs+xml',
+ 'gscript' => 'application/vnd.google-apps.script',
'gsf' => 'application/x-font-ghostscript',
'gsheet' => 'application/vnd.google-apps.spreadsheet',
+ 'gsite' => 'application/vnd.google-apps.site',
'gslides' => 'application/vnd.google-apps.presentation',
'gtar' => 'application/x-gtar',
'gtm' => 'application/vnd.groove-tool-message',
@@ -387,7 +405,6 @@
'hpid' => 'application/vnd.hp-hpid',
'hps' => 'application/vnd.hp-hps',
'hqx' => 'application/mac-binhex40',
- 'hsj2' => 'image/hsj2',
'htc' => 'text/x-component',
'htke' => 'application/vnd.kenameaapp',
'htm' => 'text/html',
@@ -399,7 +416,7 @@
'icc' => 'application/vnd.iccprofile',
'ice' => 'x-conference/x-cooltalk',
'icm' => 'application/vnd.iccprofile',
- 'ico' => 'image/x-icon',
+ 'ico' => 'image/vnd.microsoft.icon',
'ics' => 'text/calendar',
'ief' => 'image/ief',
'ifb' => 'text/calendar',
@@ -414,6 +431,7 @@
'imp' => 'application/vnd.accpac.simply.imp',
'ims' => 'application/vnd.ms-ims',
'in' => 'text/plain',
+ 'indd' => 'application/x-indesign',
'ini' => 'text/plain',
'ink' => 'application/inkml+xml',
'inkml' => 'application/inkml+xml',
@@ -421,6 +439,7 @@
'iota' => 'application/vnd.astraea-software.iota',
'ipfix' => 'application/ipfix',
'ipk' => 'application/vnd.shana.informed.package',
+ 'ipynb' => 'application/x-ipynb+json',
'irm' => 'application/vnd.ibm.rights-management',
'irp' => 'application/vnd.irepository.package+xml',
'iso' => 'application/x-iso9660-image',
@@ -430,10 +449,13 @@
'ivu' => 'application/vnd.immervision-ivu',
'jad' => 'text/vnd.sun.j2me.app-descriptor',
'jade' => 'text/jade',
+ 'jaii' => 'image/jaii',
+ 'jais' => 'image/jais',
'jam' => 'application/vnd.jam',
'jar' => 'application/java-archive',
'jardiff' => 'application/x-java-archive-diff',
'java' => 'text/x-java-source',
+ 'jfif' => 'image/jpeg',
'jhc' => 'image/jphc',
'jisp' => 'application/vnd.jisp',
'jls' => 'image/jls',
@@ -447,18 +469,19 @@
'jpf' => 'image/jpx',
'jpg' => 'image/jpeg',
'jpg2' => 'image/jp2',
- 'jpgm' => 'video/jpm',
+ 'jpgm' => 'image/jpm',
'jpgv' => 'video/jpeg',
'jph' => 'image/jph',
- 'jpm' => 'video/jpm',
+ 'jpm' => 'image/jpm',
'jpx' => 'image/jpx',
- 'js' => 'application/javascript',
+ 'js' => 'text/javascript',
'json' => 'application/json',
'json5' => 'application/json5',
'jsonld' => 'application/ld+json',
'jsonml' => 'application/jsonml+json',
'jsx' => 'text/jsx',
'jt' => 'model/jt',
+ 'jxl' => 'image/jxl',
'jxr' => 'image/jxr',
'jxra' => 'image/jxra',
'jxrs' => 'image/jxrs',
@@ -468,9 +491,10 @@
'jxss' => 'image/jxss',
'kar' => 'audio/midi',
'karbon' => 'application/vnd.kde.karbon',
+ 'kbl' => 'application/kbl+xml',
'kdb' => 'application/octet-stream',
'kdbx' => 'application/x-keepass2',
- 'key' => 'application/x-iwork-keynote-sffkey',
+ 'key' => 'application/vnd.apple.keynote',
'kfo' => 'application/vnd.kde.kformula',
'kia' => 'application/vnd.kidspiration',
'kml' => 'application/vnd.google-earth.kml+xml',
@@ -495,7 +519,7 @@
'les' => 'application/vnd.hhe.lesson-player',
'less' => 'text/less',
'lgr' => 'application/lgr+xml',
- 'lha' => 'application/octet-stream',
+ 'lha' => 'application/x-lzh-compressed',
'link66' => 'application/vnd.route66.link66+xml',
'list' => 'text/plain',
'list3820' => 'application/vnd.ibm.modcap',
@@ -504,6 +528,7 @@
'lnk' => 'application/x-ms-shortcut',
'log' => 'text/plain',
'lostxml' => 'application/lost+xml',
+ 'lottie' => 'application/zip+dotlottie',
'lrf' => 'application/octet-stream',
'lrm' => 'application/vnd.ms-lrm',
'ltf' => 'application/vnd.frogans.ltf',
@@ -511,21 +536,24 @@
'luac' => 'application/x-lua-bytecode',
'lvp' => 'audio/vnd.lucent.voice',
'lwp' => 'application/vnd.lotus-wordpro',
- 'lzh' => 'application/octet-stream',
+ 'lzh' => 'application/x-lzh-compressed',
+ 'm13' => 'application/x-msmediaview',
+ 'm14' => 'application/x-msmediaview',
'm1v' => 'video/mpeg',
+ 'm21' => 'application/mp21',
'm2a' => 'audio/mpeg',
+ 'm2t' => 'video/mp2t',
+ 'm2ts' => 'video/mp2t',
'm2v' => 'video/mpeg',
'm3a' => 'audio/mpeg',
- 'm3u' => 'text/plain',
+ 'm3u' => 'audio/x-mpegurl',
'm3u8' => 'application/vnd.apple.mpegurl',
- 'm4a' => 'audio/x-m4a',
+ 'm4a' => 'audio/mp4',
+ 'm4b' => 'audio/mp4',
'm4p' => 'application/mp4',
'm4s' => 'video/iso.segment',
- 'm4u' => 'application/vnd.mpegurl',
+ 'm4u' => 'video/vnd.mpegurl',
'm4v' => 'video/x-m4v',
- 'm13' => 'application/x-msmediaview',
- 'm14' => 'application/x-msmediaview',
- 'm21' => 'application/mp21',
'ma' => 'application/mathematica',
'mads' => 'application/mads+xml',
'maei' => 'application/mmt-aei+xml',
@@ -556,6 +584,8 @@
'mft' => 'application/rpki-manifest',
'mgp' => 'application/vnd.osgeo.mapguide.package',
'mgz' => 'application/vnd.proteus.magazine',
+ 'mht' => 'message/rfc822',
+ 'mhtml' => 'message/rfc822',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mie' => 'application/x-mie',
@@ -564,11 +594,11 @@
'mj2' => 'video/mj2',
'mjp2' => 'video/mj2',
'mjs' => 'text/javascript',
- 'mk3d' => 'video/x-matroska',
- 'mka' => 'audio/x-matroska',
+ 'mk3d' => 'video/matroska-3d',
+ 'mka' => 'audio/matroska',
'mkd' => 'text/x-markdown',
'mks' => 'video/x-matroska',
- 'mkv' => 'video/x-matroska',
+ 'mkv' => 'video/matroska',
'mlp' => 'application/vnd.dolby.mlp',
'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
'mmf' => 'application/vnd.smaf',
@@ -581,13 +611,13 @@
'mov' => 'video/quicktime',
'movie' => 'video/x-sgi-movie',
'mp2' => 'audio/mpeg',
+ 'mp21' => 'application/mp21',
'mp2a' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'mp4' => 'video/mp4',
'mp4a' => 'audio/mp4',
'mp4s' => 'application/mp4',
'mp4v' => 'video/mp4',
- 'mp21' => 'application/mp21',
'mpc' => 'application/vnd.mophun.certificate',
'mpd' => 'application/dash+xml',
'mpe' => 'video/mpeg',
@@ -612,7 +642,7 @@
'msf' => 'application/vnd.epson.msf',
'msg' => 'application/vnd.ms-outlook',
'msh' => 'model/mesh',
- 'msi' => 'application/x-msdownload',
+ 'msi' => 'application/octet-stream',
'msix' => 'application/msix',
'msixbundle' => 'application/msixbundle',
'msl' => 'application/vnd.mobius.msl',
@@ -620,7 +650,7 @@
'msp' => 'application/octet-stream',
'msty' => 'application/vnd.muvee.style',
'mtl' => 'model/mtl',
- 'mts' => 'model/vnd.mts',
+ 'mts' => 'video/mp2t',
'mus' => 'application/vnd.musician',
'musd' => 'application/mmt-usd+xml',
'musicxml' => 'application/vnd.recordare.musicxml+xml',
@@ -639,6 +669,7 @@
'nbp' => 'application/vnd.wolfram.player',
'nc' => 'application/x-netcdf',
'ncx' => 'application/x-dtbncx+xml',
+ 'ndjson' => 'application/x-ndjson',
'nfo' => 'text/x-nfo',
'ngdat' => 'application/vnd.nokia.n-gage.data',
'nitf' => 'application/vnd.nitf',
@@ -653,7 +684,7 @@
'nsf' => 'application/vnd.lotus-notes',
'nt' => 'application/n-triples',
'ntf' => 'application/vnd.nitf',
- 'numbers' => 'application/x-iwork-numbers-sffnumbers',
+ 'numbers' => 'application/vnd.apple.numbers',
'nzb' => 'application/x-nzb',
'oa2' => 'application/vnd.fujitsu.oasys2',
'oa3' => 'application/vnd.fujitsu.oasys3',
@@ -678,6 +709,8 @@
'ogv' => 'video/ogg',
'ogx' => 'application/ogg',
'omdoc' => 'application/omdoc+xml',
+ 'one' => 'application/onenote',
+ 'onea' => 'application/onenote',
'onepkg' => 'application/onenote',
'onetmp' => 'application/onenote',
'onetoc' => 'application/onenote',
@@ -686,7 +719,7 @@
'opml' => 'text/x-opml',
'oprc' => 'application/vnd.palm',
'opus' => 'audio/ogg',
- 'org' => 'text/x-org',
+ 'org' => 'application/vnd.lotus-organizer',
'osf' => 'application/vnd.yamaha.openscoreformat',
'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
'osm' => 'application/vnd.openstreetmap.data+xml',
@@ -704,17 +737,20 @@
'oxps' => 'application/oxps',
'oxt' => 'application/vnd.openofficeorg.extension',
'p' => 'text/x-pascal',
+ 'p10' => 'application/pkcs10',
+ 'p12' => 'application/x-pkcs12',
+ 'p21' => 'model/step',
'p7a' => 'application/x-pkcs7-signature',
'p7b' => 'application/x-pkcs7-certificates',
'p7c' => 'application/pkcs7-mime',
+ 'p7e' => 'application/pkcs7-mime',
'p7m' => 'application/pkcs7-mime',
'p7r' => 'application/x-pkcs7-certreqresp',
'p7s' => 'application/pkcs7-signature',
'p8' => 'application/pkcs8',
- 'p10' => 'application/x-pkcs10',
- 'p12' => 'application/x-pkcs12',
'pac' => 'application/x-ns-proxy-autoconfig',
- 'pages' => 'application/x-iwork-pages-sffpages',
+ 'pages' => 'application/vnd.apple.pages',
+ 'parquet' => 'application/vnd.apache.parquet',
'pas' => 'text/x-pascal',
'paw' => 'application/vnd.pawaafile',
'pbd' => 'application/vnd.powerbuilder6',
@@ -725,8 +761,8 @@
'pclxl' => 'application/vnd.hp-pclxl',
'pct' => 'image/x-pict',
'pcurl' => 'application/vnd.curl.pcurl',
- 'pcx' => 'image/x-pcx',
- 'pdb' => 'application/x-pilot',
+ 'pcx' => 'image/vnd.zbrush.pcx',
+ 'pdb' => 'application/vnd.palm',
'pde' => 'text/x-processing',
'pdf' => 'application/pdf',
'pem' => 'application/x-x509-user-cert',
@@ -737,7 +773,7 @@
'pfx' => 'application/x-pkcs12',
'pgm' => 'image/x-portable-graymap',
'pgn' => 'application/x-chess-pgn',
- 'pgp' => 'application/pgp',
+ 'pgp' => 'application/pgp-encrypted',
'phar' => 'application/octet-stream',
'php' => 'application/x-httpd-php',
'php3' => 'application/x-httpd-php',
@@ -760,17 +796,