4096 ? null : $v; } /** * Web Bot Auth (RFC 9421): Signature, Signature-Input and the header fields they cover, * so SenseGap can check the signature. Cookies and credentials are never sent. */ function sensegap_signature() { $input = sensegap_raw_header('Signature-Input'); $signature = sensegap_raw_header('Signature'); if (!$input || !$signature) return null; $out = ['input' => $input, 'signature' => $signature]; $private = ['cookie', 'authorization', 'proxy-authorization', 'set-cookie']; $headers = []; if (preg_match_all('/\(([^)]*)\)/', $input, $lists)) { foreach ($lists[1] as $list) { preg_match_all('/"([a-z0-9!#$%&\'*+.^_`|~-]+)"/', $list, $names); foreach ($names[1] as $name) { if ($name === 'signature-agent' || isset($headers[$name]) || in_array($name, $private, true) || count($headers) >= 8) continue; $v = sensegap_raw_header($name); if ($v !== null) $headers[$name] = $v; } } } if ($headers) $out['headers'] = $headers; return $out; } /** Same rule as every SenseGap collector: bots (known or not) and humans sent by AI products. */ function sensegap_should_send($path, $ua, $referer, $url, $signature_agent) { if (strpos($path, '/cdn-cgi/') === 0) return false; if ($ua !== null && strpos($ua, 'SenseGap-') === 0) return false; if ($signature_agent) { foreach (SENSEGAP_SIGNATURE_AGENTS as $re) if (preg_match($re, trim($signature_agent))) return true; } if ($ua === null || $ua === '') return true; foreach (SENSEGAP_BOT_UA as $re) if (preg_match($re, $ua)) return true; if (preg_match(SENSEGAP_GENERIC_BOT, $ua)) return true; $hosts = SENSEGAP_AI_HOSTS; if ($referer) { $h = strtolower((string) wp_parse_url($referer, PHP_URL_HOST)); if ($h && (in_array($h, $hosts, true) || in_array(preg_replace('/^www\./', '', $h), $hosts, true))) return true; } $query = (string) wp_parse_url($url, PHP_URL_QUERY); if ($query !== '') { parse_str($query, $q); if (isset($q['utm_source']) && is_string($q['utm_source']) && in_array(strtolower($q['utm_source']), $hosts, true)) return true; } return false; } /** Runs at the very end of each request, after the page is built. */ function sensegap_capture() { $key = sensegap_option('key'); if (!$key) return; if ((defined('DOING_CRON') && DOING_CRON) || (defined('WP_CLI') && WP_CLI)) return; if (function_exists('is_user_logged_in') && is_user_logged_in()) return; // people who log in are people $uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '/'; $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : (string) wp_parse_url(home_url(), PHP_URL_HOST); $url = (is_ssl() ? 'https' : 'http') . '://' . $host . $uri; $path = (string) wp_parse_url($url, PHP_URL_PATH); $ua = sensegap_header('User-Agent'); $referer = sensegap_header('Referer'); $signature_agent = sensegap_header('Signature-Agent'); try { if (!sensegap_should_send($path, $ua, $referer, $url, $signature_agent)) return; $ip = sensegap_client_ip(); if (!$ip) return; $start = isset($_SERVER['REQUEST_TIME_FLOAT']) ? (float) $_SERVER['REQUEST_TIME_FLOAT'] : microtime(true); $status = http_response_code(); $event = [ 'ts' => (int) round($start * 1000), 'method' => isset($_SERVER['REQUEST_METHOD']) ? substr(sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])), 0, 16) : 'GET', 'url' => $url, 'status' => is_int($status) ? $status : null, 'ip' => $ip, 'ua' => $ua, 'referer' => $referer, 'signatureAgent' => $signature_agent, 'asn' => null, 'country' => sensegap_header('CF-IPCountry'), 'ms' => (int) round((microtime(true) - $start) * 1000), ]; $sig = sensegap_signature(); if ($sig) $event['sig'] = $sig; // Hand the page to the visitor first when PHP-FPM allows it, then send. $finished = function_exists('fastcgi_finish_request') && fastcgi_finish_request(); wp_remote_post(SENSEGAP_INGEST_URL, [ 'timeout' => $finished ? 3 : 0.5, 'blocking' => $finished, 'headers' => ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $key, 'User-Agent' => 'SenseGap-WordPress/' . SENSEGAP_VERSION], 'body' => wp_json_encode(['v' => 1, 'source' => 'wordpress', 'events' => [$event]]), ]); } catch (\Throwable $e) { // Never break the site over analytics. } }