PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / logs / LogsEntrySanitizer.php

LogsEntrySanitizer.php in 404 Solution trunk, at includes/logs/LogsEntrySanitizer.php

97 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Normalizes queued log entries into the scalar storage shape accepted by
9 * logsv2 INSERTs, and serializes frontend pipeline traces for storage.
10 */
11 class ABJ_404_Solution_LogsEntrySanitizer {
12
13 /**
14 * Normalize a log entry into the strict shape expected by INSERT IGNORE.
15 * Returns null when a required column is missing or sanitization rejects
16 * the entry (empty requested_url, empty dest_url, or non-scalar payload).
17 *
18 * @param array<string, mixed> $entry
19 * @return array<string, mixed>|null
20 */
21 public function sanitizeLogEntry(array $entry): ?array {
22 $required = array('timestamp', 'user_ip', 'referrer', 'dest_url', 'requested_url', 'requested_url_detail', 'username', 'min_log_id', 'engine');
23 foreach ($required as $key) {
24 if (!array_key_exists($key, $entry)) {
25 return null;
26 }
27 }
28
29 $normalizeString = function($value, $maxLen) {
30 if (is_object($value) || is_array($value)) {
31 return null;
32 }
33 $str = (string)$value;
34 if (function_exists('mb_convert_encoding')) {
35 $converted = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
36 if (is_string($converted)) {
37 $str = $converted;
38 }
39 }
40 return substr($str, 0, $maxLen);
41 };
42
43 $sanitized = array();
44 $tsVal = $entry['timestamp'] ?? abj_clock()->now();
45 $sanitized['timestamp'] = absint(is_scalar($tsVal) ? $tsVal : abj_clock()->now());
46 $sanitized['user_ip'] = $normalizeString($entry['user_ip'], 512);
47 $sanitized['referrer'] = $normalizeString($entry['referrer'], 512);
48 $sanitized['dest_url'] = $normalizeString($entry['dest_url'], 512);
49 $sanitized['requested_url'] = $normalizeString($entry['requested_url'], 2048);
50 $sanitized['requested_url_detail'] = $normalizeString($entry['requested_url_detail'], 2048);
51 if (!is_string($sanitized['requested_url']) || $sanitized['requested_url'] === ''
52 || !is_string($sanitized['dest_url']) || $sanitized['dest_url'] === '') {
53 return null;
54 }
55
56 if (array_key_exists('canonical_url', $entry) && is_string($entry['canonical_url'])) {
57 $canonical = $entry['canonical_url'];
58 } else {
59 $canonical = '/' . trim($sanitized['requested_url'], '/');
60 }
61 $sanitized['canonical_url'] = substr($canonical, 0, 2048);
62
63 $usernameVal = $entry['username'] ?? null;
64 $sanitized['username'] = ($usernameVal === null || !is_scalar($usernameVal)) ? null : absint($usernameVal);
65 $minLogIdVal = $entry['min_log_id'] ?? null;
66 $sanitized['min_log_id'] = ($minLogIdVal === null || !is_scalar($minLogIdVal)) ? null : absint($minLogIdVal);
67 $sanitized['engine'] = $normalizeString($entry['engine'], 64);
68 if (array_key_exists('pipeline_trace', $entry)) {
69 $traceVal = $entry['pipeline_trace'];
70 $sanitized['pipeline_trace'] = ($traceVal === null || is_string($traceVal)) ? $traceVal : null;
71 } else {
72 $sanitized['pipeline_trace'] = null;
73 }
74 return $sanitized;
75 }
76
77 /**
78 * gz + base64 the pipeline trace payload for storage.
79 *
80 * @param array<int, array{step: string, outcome: string, detail: string}>|null $trace
81 */
82 public function serializePipelineTrace(?array $trace): ?string {
83 if ($trace === null || empty($trace)) {
84 return null;
85 }
86 $json = json_encode($trace);
87 if ($json === false) {
88 return null;
89 }
90 $compressed = gzcompress($json, 6);
91 if ($compressed === false) {
92 return null;
93 }
94 return base64_encode($compressed);
95 }
96 }
97