PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
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 / core / Sanitizer.php

Sanitizer.php in 404 Solution 4.3.0, at includes/core/Sanitizer.php

165 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 /**
9 * String, URL, and character-set sanitization service.
10 *
11 * Responsibilities:
12 * - sanitize_text_field_recursive: array/scalar recursive WP sanitize_text_field
13 * - escapeForXSS: strip control characters and HTML-significant punctuation
14 * - normalizeUrlString: trim + optional rawurldecode + strip invalid UTF-8/control bytes
15 * - sanitizeUrlComponent: strip invalid UTF-8/control bytes from a URL component
16 * - containsUtf8mb4Characters: detect 4-byte UTF-8 sequences (utf8mb4-only codepoints)
17 *
18 * Extracted from ABJ_404_Solution_Functions per design-audit-2026-06-02
19 * M201 (Functions.php grab-bag split, parent task i802). This is a
20 * sanitization concern distinct from the polymorphic mbstring/preg
21 * adapter and from URL percent-encoding (UrlEncoder).
22 *
23 * Depends on ABJ_404_Solution_MbStringAdapter for sanitizeInvalidUTF8().
24 * Injected via constructor - Sanitizer does not need the rest of the
25 * Functions utility surface, so it depends on the smaller adapter
26 * interface directly (sibling task i825 extracted the adapter).
27 */
28 class ABJ_404_Solution_Sanitizer {
29
30 /** @var ABJ_404_Solution_MbStringAdapter */
31 private $mbAdapter;
32
33 /**
34 * Accepts either an MbStringAdapter (the focused dependency, preferred
35 * for new callers) or the legacy ABJ_404_Solution_Functions kitchen
36 * sink (which carries an MbStringAdapter internally). The Functions
37 * variant is kept for backward compatibility with test fixtures and
38 * older wiring that has not yet migrated.
39 *
40 * @param ABJ_404_Solution_MbStringAdapter|ABJ_404_Solution_Functions $adapter
41 */
42 public function __construct($adapter) {
43 if ($adapter instanceof ABJ_404_Solution_MbStringAdapter) {
44 $this->mbAdapter = $adapter;
45 } else if ($adapter instanceof ABJ_404_Solution_Functions) {
46 $this->mbAdapter = $adapter->getMbStringAdapter();
47 } else {
48 throw new InvalidArgumentException(
49 'ABJ_404_Solution_Sanitizer requires an MbStringAdapter or Functions instance; got '
50 . (is_object($adapter) ? get_class($adapter) : gettype($adapter))
51 );
52 }
53 }
54
55 /**
56 * Recursively applies `sanitize_text_field` to strings in an array or other data structure.
57 * @param mixed $data The data to sanitize. If an array, will recursively
58 * apply this function to all elements.
59 * @return mixed The sanitized data.
60 */
61 public function sanitize_text_field_recursive($data) {
62 if (is_array($data)) {
63 return array_map([$this, 'sanitize_text_field_recursive'], $data);
64 }
65
66 return sanitize_text_field(is_string($data) ? $data : (is_scalar($data) ? (string)$data : ''));
67 }
68
69 /** Escape a string to avoid Cross Site Scripting (XSS) attacks by encoding unsafe HTML characters.
70 * @param string|null $value The string to be escaped.
71 * @return string The escaped string.
72 */
73 public function escapeForXSS(?string $value): string {
74 if ($value === null) {
75 return '';
76 }
77 // Remove control characters and other unsafe characters
78 $value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value) ?? '';
79 // Remove any other characters you consider unsafe
80 $value = preg_replace('/[<>"\'`{}()]/u', '', $value) ?? '';
81
82 return $value;
83 }
84
85 /**
86 * Normalize a URL string for storage or matching.
87 * - Optionally decode percent-encoded octets
88 * - Strip invalid UTF-8/control bytes
89 *
90 * Accepts `mixed` (matching the original Functions::normalizeUrlString
91 * signature it was extracted from) because callers reach for it with
92 * raw `$_SERVER`/`$_COOKIE`/option-row values whose static type is
93 * `mixed`. Non-string scalars are coerced; null/empty short-circuit
94 * to ''.
95 *
96 * @param mixed $url
97 * @param array<string, bool> $options Supported keys: decode (bool)
98 * @return string
99 */
100 public function normalizeUrlString($url, array $options = array()) {
101 $options = array_merge(array('decode' => true), $options);
102
103 if ($url === null || $url === '') {
104 return '';
105 }
106
107 if (!is_string($url)) {
108 $url = is_scalar($url) ? strval($url) : '';
109 }
110
111 $url = trim($url);
112 if ($options['decode']) {
113 $url = rawurldecode($url);
114 }
115
116 $url = $this->mbAdapter->sanitizeInvalidUTF8($url);
117 // Remove remaining control characters (keep whitespace)
118 $url = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $url) ?? $url;
119
120 return $url;
121 }
122
123 /**
124 * Sanitize URL components without stripping reserved characters.
125 * Keeps characters like ()[]{} for matching but removes invalid UTF-8/control bytes.
126 *
127 * @param mixed $value
128 * @return mixed
129 */
130 public function sanitizeUrlComponent($value) {
131 if (is_array($value)) {
132 return array_map([$this, 'sanitizeUrlComponent'], $value);
133 }
134
135 if ($value === null || $value === '') {
136 return '';
137 }
138
139 if (!is_string($value)) {
140 $value = is_scalar($value) ? strval($value) : '';
141 }
142
143 $value = $this->mbAdapter->sanitizeInvalidUTF8($value);
144 $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value);
145
146 return $value;
147 }
148
149 /**
150 * Check whether a string contains any UTF-8 4-byte characters (codepoints > U+FFFF).
151 * These characters require utf8mb4 storage; they cannot exist in a utf8mb3 or latin1 column.
152 *
153 * @param string $string
154 * @return bool true if the string contains at least one 4-byte UTF-8 character
155 */
156 public function containsUtf8mb4Characters(string $string): bool {
157 if ($string === '') {
158 return false;
159 }
160 // 4-byte UTF-8 sequences start with a byte in the range F0-F4
161 // followed by three continuation bytes (80-BF).
162 return (bool) preg_match('/[\xF0-\xF4][\x80-\xBF]{3}/', $string);
163 }
164 }
165