200 || preg_match('/[\x{0000}-\x{001F}\x{007F}-\x{009F}]/u', $value)) { continue; } $identity[$name] = $value; } ksort($identity, SORT_STRING); if (strlen(http_build_query($identity, '', '&', PHP_QUERY_RFC3986)) > 2048) { $identity = array(); } $attribution = array(); foreach (array('utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term') as $key) { if (isset($query[$key]) && is_string($query[$key])) { $attribution[$key] = $query[$key]; } } $retained = array_merge($identity, $attribution); return $safe . ($retained ? '?' . http_build_query($retained, '', '&', PHP_QUERY_RFC3986) : ''); } function airefs_tracking_valid_timestamp($timestamp) { if (!is_string($timestamp) || !preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,6})?(?:Z|([+-])(\d{2}):(\d{2}))$/', $timestamp, $parts)) { return false; } return checkdate((int) $parts[2], (int) $parts[3], (int) $parts[1]) && (int) $parts[4] < 24 && (int) $parts[5] < 60 && (int) $parts[6] < 60 && (!isset($parts[8]) || ((int) $parts[8] < 24 && (int) $parts[9] < 60)); } function airefs_tracking_validate_request($result, $server, $request) { if (preg_match('@^/airefs/v1/events$@i', rtrim($request->get_route(), '/')) !== 1) { return $result; } if (strlen($request->get_body()) > 16384) { return new WP_Error('event_too_large', 'Event body exceeds 16 KiB.', array('status' => 413)); } $origin = $request->get_header('origin'); if ($origin !== null && $origin !== '' && airefs_tracking_origin($origin) !== airefs_tracking_origin(home_url('/'))) { return new WP_Error('invalid_origin', 'This event must originate from this site.', array('status' => 403)); } return $result; } add_filter('rest_pre_dispatch', 'airefs_tracking_validate_request', 10, 3); /** Atomic per-IP fixed-minute counter; cleanup reads the window stored in the same row. */ function airefs_tracking_allow_request($ip) { global $wpdb; $window = (int) floor(time() / 60); $table = $wpdb->base_prefix . 'options'; $key = airefs_tracking_rate_prefix() . hash_hmac('sha256', $ip, wp_salt('auth')); $name = '_' . $key; $value = $window . ':1'; $inserted = $wpdb->query($wpdb->prepare("INSERT IGNORE INTO $table (option_name, option_value, autoload) VALUES (%s, %s, 'no')", $name, $value)); if ($inserted !== 1) { $updated = $wpdb->query($wpdb->prepare( "UPDATE $table SET option_value = IF(CAST(SUBSTRING_INDEX(option_value, ':', 1) AS UNSIGNED) < %s, %s, CONCAT(%s, ':', CAST(SUBSTRING_INDEX(option_value, ':', -1) AS UNSIGNED) + 1)) WHERE option_name = %s AND (CAST(SUBSTRING_INDEX(option_value, ':', 1) AS UNSIGNED) < %s OR (SUBSTRING_INDEX(option_value, ':', 1) = %s AND CAST(SUBSTRING_INDEX(option_value, ':', -1) AS UNSIGNED) < 100))", (string) $window, $value, (string) $window, $name, (string) $window, (string) $window )); if ($updated !== 1) { return false; } } return true; } function airefs_tracking_rate_prefix($network = false) { return 'airefs_rate_limit_n' . (is_multisite() ? get_current_network_id() : 1) . '_' . ($network ? '' : 'b' . get_current_blog_id() . '_'); } function airefs_tracking_clear_rate_limits($network = false) { global $wpdb; $table = $wpdb->base_prefix . 'options'; $wpdb->query($wpdb->prepare("DELETE FROM $table WHERE option_name LIKE %s", $wpdb->esc_like('_' . airefs_tracking_rate_prefix($network)) . '%')); } /** Direct database counters need cleanup even when WordPress uses an external object cache. */ function airefs_tracking_cleanup_rate_limits() { global $wpdb; $table = $wpdb->base_prefix . 'options'; $name = '_airefs_rate_cleanup'; $wpdb->query($wpdb->prepare("INSERT IGNORE INTO $table (option_name, option_value, autoload) VALUES (%s, '0', 'no')", $name)); $claimed = $wpdb->query($wpdb->prepare("UPDATE $table SET option_value = %s WHERE option_name = %s AND CAST(option_value AS UNSIGNED) <= %d", (string) (time() + 60), $name, time())); if ($claimed !== 1) { return; } $pattern = $wpdb->esc_like('_airefs_rate_limit_') . '%'; $wpdb->query($wpdb->prepare("DELETE FROM $table WHERE option_name LIKE %s AND CAST(SUBSTRING_INDEX(option_value, ':', 1) AS UNSIGNED) < %d", $pattern, (int) floor(time() / 60))); } add_action('init', 'airefs_tracking_cleanup_rate_limits');