PluginProbe
404 Solution / 4.2.0
404 Solution v4.2.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 / php / FunctionsPreg.php

FunctionsPreg.php in 404 Solution 4.2.0, at includes/php/FunctionsPreg.php

186 lines 6.5 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 /* Static functions that can be used from anywhere. */
9 class ABJ_404_Solution_FunctionsPreg extends ABJ_404_Solution_Functions {
10
11 /** @var self|null */
12 private static $instance = null;
13
14 public static function getInstance(): self {
15 if (self::$instance == null) {
16 self::$instance = new ABJ_404_Solution_FunctionsPreg();
17 }
18
19 return self::$instance;
20 }
21
22 /** Use this to find a delimiter.
23 * @var array<int, string> */
24 private $delimiterChars = array('`', '^', '|', '~', '!', ';', ':', ',', '@', "'", '/');
25
26 function ord(string $char): int {
27 return ord($char);
28 }
29
30 function strtolower(string $string): string {
31 return strtolower($string);
32 }
33
34 function strlen(string $string): int {
35 return strlen($string);
36 }
37
38 /** @return int|false */
39 function strpos(string $haystack, string $needle, int $offset = 0) {
40 if ($offset == 0) {
41 return strpos($haystack, $needle);
42 }
43 return strpos($haystack, $needle, $offset);
44 }
45
46 function substr(string $str, int $start, ?int $length = null): string {
47 if ($length === null) {
48 return substr($str, $start);
49 }
50 return substr($str, $start, $length);
51 }
52
53 /**
54 * @param array<int, string>|null $regs
55 * @return bool|int
56 */
57 function regexMatch(string $pattern, string $string, ?array &$regs = null) {
58 // find a character to use for quotes
59 $delimiterA = "{";
60 $delimiterB = "}";
61 if (strpos($pattern, "}") !== false) {
62 $delimiterA = $delimiterB = $this->findADelimiter($pattern);
63 }
64 return preg_match($delimiterA . $pattern . $delimiterB, $string, $regs);
65 }
66
67 /**
68 * @param array<int, string>|null $regs
69 * @return bool|int
70 */
71 function regexMatchi(string $pattern, string $string, ?array &$regs = null) {
72 // find a character to use for quotes
73 $delimiterA = "{";
74 $delimiterB = "}";
75 if (strpos($pattern, "}") !== false) {
76 $delimiterA = $delimiterB = $this->findADelimiter($pattern);
77 }
78 return preg_match($delimiterA . $pattern . $delimiterB . 'i', $string, $regs);
79 }
80
81 /** @return string|null */
82 function regexReplace($pattern, $replacement, $string) {
83 // find a character to use for quotes
84 $delimiterA = "{";
85 $delimiterB = "}";
86 if (strpos($pattern, "}") !== false) {
87 $delimiterA = $delimiterB = $this->findADelimiter($pattern);
88 }
89 $replacementDelimiter = $this->findADelimiter($replacement);
90 $replacement = preg_replace($replacementDelimiter . '\\\\' . $replacementDelimiter, '\$', $replacement) ?? $replacement;
91 return preg_replace($delimiterA . $pattern . $delimiterB, $replacement, $string);
92 }
93
94 function findADelimiter(string $pattern): string {
95 if ($pattern == '') {
96 return $this->delimiterChars[0];
97 }
98
99 $charToUse = null;
100 foreach ($this->delimiterChars as $char) {
101 if ($char === '') { continue; }
102 $anArray = explode($char, $pattern);
103 if (sizeof($anArray) == 1) {
104 $charToUse = $char;
105 break;
106 }
107 }
108
109 if ($charToUse == null) {
110 throw new Exception("I can't find a valid delimiter character to use for the regular expression: "
111 . esc_html($pattern));
112 }
113
114 return $charToUse;
115 }
116
117 /**
118 * Sanitize invalid UTF-8 byte sequences from a string.
119 *
120 * This is the fallback implementation for systems without mbstring extension.
121 * It uses preg_replace with the 'u' modifier to remove invalid UTF-8 sequences.
122 *
123 * The approach:
124 * 1. Use iconv if available (faster and more reliable)
125 * 2. Fall back to preg_replace to remove non-UTF-8 bytes
126 * 3. Remove control characters that cause database issues
127 *
128 * @param string|null $string The string to sanitize
129 * @return string The sanitized string with only valid UTF-8 characters
130 */
131 function sanitizeInvalidUTF8(?string $string): string {
132 // Handle null and empty cases
133 if ($string === null || $string === '') {
134 return '';
135 }
136
137 // Convert to string if not already
138 if (!is_string($string)) {
139 $string = strval($string);
140 }
141
142 // Try iconv first (if available, it's very efficient)
143 if (function_exists('iconv')) {
144 // iconv with //IGNORE will skip invalid UTF-8 sequences
145 // iconv can emit notices on malformed input; we treat those as expected and fall back.
146 $sanitized = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
147
148 // iconv returns false on error, fall through to preg approach
149 if ($sanitized !== false) {
150 // Remove null bytes and problematic control characters
151 $sanitized = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/u', '', $sanitized) ?? $sanitized;
152 return $sanitized;
153 }
154 }
155
156 // Fallback: use preg_replace with 'u' modifier to validate UTF-8
157 // The //u modifier forces UTF-8 mode - invalid sequences cause match failure
158 // By replacing '' with '', we essentially validate and keep only valid UTF-8
159 $sanitized = @preg_replace('//u', '', $string);
160
161 // If preg_replace failed (invalid UTF-8), use byte-by-byte filtering
162 if ($sanitized === null) {
163 // Filter out invalid UTF-8 lead bytes:
164 // - C0, C1 (overlong 2-byte sequences)
165 // - F5-FF (invalid lead bytes beyond UTF-8 range)
166 // Keep valid ranges: C2-DF (2-byte), E0-EF (3-byte), F0-F4 (4-byte)
167 $sanitized = preg_replace('/[\xC0\xC1\xF5-\xFF][\x80-\xBF]*/', '', $string) ?? $string;
168
169 // Remove incomplete sequences (continuation bytes without lead byte)
170 $sanitized = preg_replace('/[\x80-\xBF]+/', '', $sanitized) ?? $sanitized;
171
172 // Verify the result is now valid UTF-8 by attempting a UTF-8 match
173 if (@preg_match('//u', $sanitized) === false) {
174 // Still invalid - fall back to ASCII-only (safe but lossy)
175 $sanitized = preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $string) ?? '';
176 }
177 }
178
179 // Remove null bytes and other problematic control characters
180 $sanitized = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $sanitized) ?? $sanitized;
181
182 return $sanitized;
183 }
184
185 }
186