$w3tc_sns_max_body_bytes ) { w3tc_sns_reject( 413, 'Payload too large' ); } /** * Read at most max+1 bytes; if the stream still has data we know the payload * exceeds the cap and reject. We deliberately consume php://input here so the * SDK's `fromRawPostData()` (which would re-read the stream unbounded) is not * used downstream. */ $w3tc_sns_input_handle = fopen( 'php://input', 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- pre-WP-bootstrap; WP_Filesystem unavailable. if ( false === $w3tc_sns_input_handle ) { w3tc_sns_reject( 400, 'Cannot read body' ); } $w3tc_sns_body = stream_get_contents( $w3tc_sns_input_handle, $w3tc_sns_max_body_bytes + 1 ); fclose( $w3tc_sns_input_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- pre-WP-bootstrap; WP_Filesystem unavailable. if ( false === $w3tc_sns_body || strlen( $w3tc_sns_body ) > $w3tc_sns_max_body_bytes ) { w3tc_sns_reject( 413, 'Payload too large' ); } /** * 4. Locate the plugin's vendored Composer autoloader so we can use the AWS * SNS message validator WITHOUT bootstrapping WordPress. This file lives in * `/pub/sns.php`, so the autoloader is one level up. */ $w3tc_sns_autoload = __DIR__ . '/../vendor/autoload.php'; if ( ! file_exists( $w3tc_sns_autoload ) ) { w3tc_sns_reject( 500, 'Server misconfigured' ); } require_once $w3tc_sns_autoload; if ( ! class_exists( '\Aws\Sns\Message' ) || ! class_exists( '\Aws\Sns\MessageValidator' ) ) { w3tc_sns_reject( 500, 'Server misconfigured' ); } /** * 5. Build the SNS Message directly from the bounded body we read in step 3. * The SDK's `fromRawPostData()` re-reads php://input without a size cap and * `fromJsonString()` would re-decode the body, so we decode once and pass the * array to the constructor. Catch \Throwable rather than \Exception so future * SDK upgrades that throw Error subtypes (e.g. TypeError on a missing key) do * not bypass the 400 response and surface a 500 stack trace. */ $w3tc_sns_decoded = json_decode( $w3tc_sns_body, true ); if ( ! is_array( $w3tc_sns_decoded ) ) { w3tc_sns_reject( 400, 'Invalid SNS message' ); } try { $w3tc_sns_message = new \Aws\Sns\Message( $w3tc_sns_decoded ); } catch ( \Throwable $e ) { w3tc_sns_reject( 400, 'Invalid SNS message' ); } /** * 6. Cryptographically validate the message signature against AWS's * published SNS signing certificates before we trust ANY field on it. The * validator throws on any failure (bad signature, untrusted cert host, etc). */ try { $w3tc_sns_validator = new \Aws\Sns\MessageValidator(); $w3tc_sns_validator->validate( $w3tc_sns_message ); } catch ( \Throwable $e ) { w3tc_sns_reject( 403, 'Forbidden' ); } /** * 7. Signature verified. NOW we can load WordPress and hand the message off * to the existing handler for TopicArn matching and action dispatch. * * Note: we deliberately do NOT mutate `$_SERVER['HTTP_HOST']` or * `$w3tc_w3_current_blog_id` here. Any multisite/blog-context switching the SNS * payload requests is performed inside `Enterprise_SnsServer` AFTER * validation, against an allowlist of configured site hostnames. */ if ( ! defined( 'W3TC_WP_LOADING' ) ) { define( 'W3TC_WP_LOADING', true ); } if ( ! defined( 'ABSPATH' ) ) { /** * Standard layout: this file lives at * `wp-content/plugins/w3-total-cache/pub/sns.php`, so wp-load.php is four * directories up. Sites that vendor W3TC outside `wp-content/plugins/` * (must-use plugin dirs, custom plugin directories, some managed-host * bundles) ship a shim at `/../w3tc-wp-loader.php`; fall back to * that before erroring so non-standard installs don't 500 on every SNS * POST after the upgrade. */ $w3tc_sns_wp_load = __DIR__ . '/../../../../wp-load.php'; if ( ! file_exists( $w3tc_sns_wp_load ) ) { $w3tc_sns_wp_load = __DIR__ . '/../../w3tc-wp-loader.php'; } if ( ! file_exists( $w3tc_sns_wp_load ) ) { w3tc_sns_reject( 500, 'WordPress not found' ); } require_once $w3tc_sns_wp_load; } if ( ! defined( 'W3TC_DIR' ) ) { define( 'W3TC_DIR', realpath( __DIR__ . '/..' ) ); } if ( ! is_dir( W3TC_DIR ) || ! file_exists( W3TC_DIR . '/w3-total-cache-api.php' ) ) { w3tc_sns_reject( 500, 'Plugin files missing' ); } require_once W3TC_DIR . '/w3-total-cache-api.php'; /** * Convert the validated SNS Message back to a plain array for the existing * handler signature (it constructs its own `\Aws\Sns\Message` internally and * re-validates as belt-and-braces). */ $w3tc_sns_message_array = $w3tc_sns_message->toArray(); $w3tc_sns_server = \W3TC\Dispatcher::component( 'Enterprise_SnsServer' ); $w3tc_sns_server->process_message( $w3tc_sns_message_array );