Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Config / Validator.php
blobd3dca2780fd141f1f08a9cc0879355db35b4f0a9
1 <?php
2 /**
3 * Form validation for configuration editor
4 */
6 declare(strict_types=1);
8 namespace PhpMyAdmin\Config;
10 use PhpMyAdmin\Config;
11 use PhpMyAdmin\Core;
12 use PhpMyAdmin\Sanitize;
13 use PhpMyAdmin\Util;
15 use function __;
16 use function array_map;
17 use function array_merge;
18 use function array_shift;
19 use function call_user_func_array;
20 use function count;
21 use function error_clear_last;
22 use function error_get_last;
23 use function explode;
24 use function filter_var;
25 use function htmlspecialchars;
26 use function is_array;
27 use function is_object;
28 use function mb_strpos;
29 use function mb_substr;
30 use function mysqli_close;
31 use function mysqli_connect;
32 use function mysqli_report;
33 use function preg_match;
34 use function preg_replace;
35 use function sprintf;
36 use function str_replace;
37 use function str_starts_with;
38 use function trim;
40 use const FILTER_FLAG_IPV4;
41 use const FILTER_FLAG_IPV6;
42 use const FILTER_VALIDATE_IP;
43 use const MYSQLI_REPORT_OFF;
44 use const PHP_INT_MAX;
46 /**
47 * Validation class for various validation functions
49 * Validation function takes two argument: id for which it is called
50 * and array of fields' values (usually values for entire formset).
51 * The function must always return an array with an error (or error array)
52 * assigned to a form element (formset name or field path). Even if there are
53 * no errors, key must be set with an empty value.
55 * Validation functions are assigned in $cfg_db['_validators'] (config.values.php).
57 class Validator
59 /**
60 * Returns validator list
62 * @param ConfigFile $cf Config file instance
64 * @return mixed[]
66 public static function getValidators(ConfigFile $cf): array
68 static $validators = null;
70 if ($validators !== null) {
71 return $validators;
74 $validators = $cf->getDbEntry('_validators', []);
75 $config = Config::getInstance();
76 if ($config->get('is_setup')) {
77 return $validators;
80 // not in setup script: load additional validators for user
81 // preferences we need original config values not overwritten
82 // by user preferences, creating a new PhpMyAdmin\Config instance is a
83 // better idea than hacking into its code
84 $uvs = $cf->getDbEntry('_userValidators', []);
85 foreach ($uvs as $field => $uvList) {
86 $uvList = (array) $uvList;
87 foreach ($uvList as &$uv) {
88 if (! is_array($uv)) {
89 continue;
92 for ($i = 1, $nb = count($uv); $i < $nb; $i++) {
93 if (! str_starts_with($uv[$i], 'value:')) {
94 continue;
97 $uv[$i] = Core::arrayRead(
98 mb_substr($uv[$i], 6),
99 $config->baseSettings,
104 $validators[$field] = isset($validators[$field])
105 ? array_merge((array) $validators[$field], $uvList)
106 : $uvList;
109 return $validators;
113 * Runs validation $validator_id on values $values and returns error list.
115 * Return values:
116 * o array, keys - field path or formset id, values - array of errors
117 * when $isPostSource is true values is an empty array to allow for error list
118 * cleanup in HTML document
119 * o false - when no validators match name(s) given by $validator_id
121 * @param ConfigFile $cf Config file instance
122 * @param string|mixed[] $validatorId ID of validator(s) to run
123 * @param mixed[] $values Values to validate
124 * @param bool $isPostSource tells whether $values are directly from
125 * POST request
127 public static function validate(
128 ConfigFile $cf,
129 string|array $validatorId,
130 array $values,
131 bool $isPostSource,
132 ): bool|array {
133 // find validators
134 $validatorId = (array) $validatorId;
135 $validators = static::getValidators($cf);
136 $vids = [];
137 foreach ($validatorId as &$vid) {
138 $vid = $cf->getCanonicalPath($vid);
139 if (! isset($validators[$vid])) {
140 continue;
143 $vids[] = $vid;
146 if ($vids === []) {
147 return false;
150 // create argument list with canonical paths and remember path mapping
151 $arguments = [];
152 $keyMap = [];
153 foreach ($values as $k => $v) {
154 $k2 = $isPostSource ? str_replace('-', '/', $k) : $k;
155 $k2 = mb_strpos($k2, '/')
156 ? $cf->getCanonicalPath($k2)
157 : $k2;
158 $keyMap[$k2] = $k;
159 $arguments[$k2] = $v;
162 // validate
163 $result = [];
164 foreach ($vids as $vid) {
165 // call appropriate validation functions
166 foreach ((array) $validators[$vid] as $validator) {
167 $vdef = (array) $validator;
168 $vname = array_shift($vdef);
169 /** @var callable $vname */
170 $vname = 'PhpMyAdmin\Config\Validator::' . $vname;
171 $args = array_merge([$vid, &$arguments], $vdef);
172 $r = call_user_func_array($vname, $args);
174 // merge results
175 if (! is_array($r)) {
176 continue;
179 foreach ($r as $key => $errorList) {
180 // skip empty values if $isPostSource is false
181 if (! $isPostSource && empty($errorList)) {
182 continue;
185 if (! isset($result[$key])) {
186 $result[$key] = [];
189 $errorList = array_map(Sanitize::convertBBCode(...), (array) $errorList);
190 $result[$key] = array_merge($result[$key], $errorList);
195 // restore original paths
196 $newResult = [];
197 foreach ($result as $k => $v) {
198 $k2 = $keyMap[$k] ?? $k;
199 $newResult[$k2] = $v;
202 return $newResult === [] ? true : $newResult;
206 * Test database connection
208 * @param string $host host name
209 * @param string $port tcp port to use
210 * @param string $socket socket to use
211 * @param string $user username to use
212 * @param string $pass password to use
213 * @param string $errorKey key to use in return array
215 public static function testDBConnection(
216 string $host,
217 string $port,
218 string $socket,
219 string $user,
220 string $pass,
221 string $errorKey = 'Server',
222 ): bool|array {
223 $config = Config::getInstance();
224 if ($config->config->debug->demo) {
225 // Connection test disabled on the demo server!
226 return true;
229 $error = null;
230 $host = Core::sanitizeMySQLHost($host);
232 error_clear_last();
234 /** @var string $socket */
235 $socket = $socket === '' ? null : $socket;
236 /** @var int $port */
237 $port = $port === '' ? null : (int) $port;
239 mysqli_report(MYSQLI_REPORT_OFF);
241 $conn = @mysqli_connect($host, $user, $pass, '', $port, $socket);
242 if (! $conn) {
243 $error = __('Could not connect to the database server!');
244 } else {
245 mysqli_close($conn);
248 if ($error !== null) {
249 $lastError = error_get_last();
250 if ($lastError !== null) {
251 $error .= ' - ' . $lastError['message'];
255 return $error === null ? true : [$errorKey => $error];
259 * Validate server config
261 * @param string $path path to config, not used
262 * keep this parameter since the method is invoked using
263 * reflection along with other similar methods
264 * @param mixed[] $values config values
266 * @return mixed[]
268 public static function validateServer(string $path, array $values): array
270 $result = [
271 'Server' => '',
272 'Servers/1/user' => '',
273 'Servers/1/SignonSession' => '',
274 'Servers/1/SignonURL' => '',
276 $error = false;
277 if (empty($values['Servers/1/auth_type'])) {
278 $values['Servers/1/auth_type'] = '';
279 $result['Servers/1/auth_type'] = __('Invalid authentication type!');
280 $error = true;
283 if ($values['Servers/1/auth_type'] === 'config' && empty($values['Servers/1/user'])) {
284 $result['Servers/1/user'] = __('Empty username while using [kbd]config[/kbd] authentication method!');
285 $error = true;
288 if ($values['Servers/1/auth_type'] === 'signon' && empty($values['Servers/1/SignonSession'])) {
289 $result['Servers/1/SignonSession'] = __(
290 'Empty signon session name while using [kbd]signon[/kbd] authentication method!',
292 $error = true;
295 if ($values['Servers/1/auth_type'] === 'signon' && empty($values['Servers/1/SignonURL'])) {
296 $result['Servers/1/SignonURL'] = __(
297 'Empty signon URL while using [kbd]signon[/kbd] authentication method!',
299 $error = true;
302 if (! $error && $values['Servers/1/auth_type'] === 'config') {
303 $password = '';
304 if (! empty($values['Servers/1/password'])) {
305 $password = $values['Servers/1/password'];
308 $test = static::testDBConnection(
309 empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'],
310 empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'],
311 empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'],
312 empty($values['Servers/1/user']) ? '' : $values['Servers/1/user'],
313 $password,
316 if (is_array($test)) {
317 $result = array_merge($result, $test);
321 return $result;
325 * Validate pmadb config
327 * @param string $path path to config, not used
328 * keep this parameter since the method is invoked using
329 * reflection along with other similar methods
330 * @param mixed[] $values config values
332 * @return mixed[]
334 public static function validatePMAStorage(string $path, array $values): array
336 $result = [
337 'Server_pmadb' => '',
338 'Servers/1/controluser' => '',
339 'Servers/1/controlpass' => '',
341 $error = false;
343 if (empty($values['Servers/1/pmadb'])) {
344 return $result;
347 $result = [];
348 if (empty($values['Servers/1/controluser'])) {
349 $result['Servers/1/controluser'] = __(
350 'Empty phpMyAdmin control user while using phpMyAdmin configuration storage!',
352 $error = true;
355 if (empty($values['Servers/1/controlpass'])) {
356 $result['Servers/1/controlpass'] = __(
357 'Empty phpMyAdmin control user password while using phpMyAdmin configuration storage!',
359 $error = true;
362 if (! $error) {
363 $test = static::testDBConnection(
364 empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'],
365 empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'],
366 empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'],
367 empty($values['Servers/1/controluser']) ? '' : $values['Servers/1/controluser'],
368 empty($values['Servers/1/controlpass']) ? '' : $values['Servers/1/controlpass'],
369 'Server_pmadb',
371 if (is_array($test)) {
372 $result = array_merge($result, $test);
376 return $result;
380 * Validates regular expression
382 * @param string $path path to config
383 * @param mixed[] $values config values
385 * @return mixed[]
387 public static function validateRegex(string $path, array $values): array
389 $result = [$path => ''];
391 if (empty($values[$path])) {
392 return $result;
395 error_clear_last();
397 $matches = [];
398 // in libraries/ListDatabase.php _checkHideDatabase(),
399 // a '/' is used as the delimiter for hide_db
400 @preg_match('/' . Util::requestString($values[$path]) . '/', '', $matches);
402 $currentError = error_get_last();
404 if ($currentError !== null) {
405 $error = preg_replace('/^preg_match\(\): /', '', $currentError['message']);
407 return [$path => $error];
410 return $result;
414 * Validates TrustedProxies field
416 * @param string $path path to config
417 * @param mixed[] $values config values
419 * @return mixed[]
421 public static function validateTrustedProxies(string $path, array $values): array
423 $result = [$path => []];
425 if (empty($values[$path])) {
426 return $result;
429 if (is_array($values[$path]) || is_object($values[$path])) {
430 // value already processed by FormDisplay::save
431 $lines = [];
432 foreach ($values[$path] as $ip => $v) {
433 $v = Util::requestString($v);
434 $lines[] = preg_match('/^-\d+$/', $ip)
435 ? $v
436 : $ip . ': ' . $v;
438 } else {
439 // AJAX validation
440 $lines = explode("\n", $values[$path]);
443 foreach ($lines as $line) {
444 $line = trim($line);
445 $matches = [];
446 // we catch anything that may (or may not) be an IP
447 if (! preg_match('/^(.+):(?:[ ]?)\\w+$/', $line, $matches)) {
448 $result[$path][] = __('Incorrect value:') . ' '
449 . htmlspecialchars($line);
450 continue;
453 // now let's check whether we really have an IP address
454 if (
455 filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false
456 && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false
458 $ip = htmlspecialchars(trim($matches[1]));
459 $result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip);
460 continue;
464 return $result;
468 * Tests integer value
470 * @param string $path path to config
471 * @param mixed[] $values config values
472 * @param bool $allowNegative allow negative values
473 * @param bool $allowZero allow zero
474 * @param int $maxValue max allowed value
475 * @param string $errorString error message string
477 * @return string empty string if test is successful
479 public static function validateNumber(
480 string $path,
481 array $values,
482 bool $allowNegative,
483 bool $allowZero,
484 int $maxValue,
485 string $errorString,
486 ): string {
487 if (empty($values[$path])) {
488 return '';
491 $value = Util::requestString($values[$path]);
493 if (
494 (int) $value != $value
495 || (! $allowNegative && $value < 0)
496 || (! $allowZero && $value == 0)
497 || $value > $maxValue
499 return $errorString;
502 return '';
506 * Validates port number
508 * @param string $path path to config
509 * @param mixed[] $values config values
511 * @return mixed[]
513 public static function validatePortNumber(string $path, array $values): array
515 return [
516 $path => static::validateNumber(
517 $path,
518 $values,
519 false,
520 false,
521 65535,
522 __('Not a valid port number!'),
528 * Validates positive number
530 * @param string $path path to config
531 * @param mixed[] $values config values
533 * @return mixed[]
535 public static function validatePositiveNumber(string $path, array $values): array
537 return [
538 $path => static::validateNumber(
539 $path,
540 $values,
541 false,
542 false,
543 PHP_INT_MAX,
544 __('Not a positive number!'),
550 * Validates non-negative number
552 * @param string $path path to config
553 * @param mixed[] $values config values
555 * @return mixed[]
557 public static function validateNonNegativeNumber(string $path, array $values): array
559 return [
560 $path => static::validateNumber(
561 $path,
562 $values,
563 false,
564 true,
565 PHP_INT_MAX,
566 __('Not a non-negative number!'),
572 * Validates value according to given regular expression
573 * Pattern and modifiers must be a valid for PCRE <b>and</b> JavaScript RegExp
575 * @param string $path path to config
576 * @param mixed[] $values config values
577 * @param non-empty-string $regex regular expression to match
579 public static function validateByRegex(string $path, array $values, string $regex): array|string
581 if (! isset($values[$path])) {
582 return '';
585 $result = preg_match($regex, Util::requestString($values[$path]));
587 return [$path => $result ? '' : __('Incorrect value!')];
591 * Validates upper bound for numeric inputs
593 * @param string $path path to config
594 * @param mixed[] $values config values
595 * @param int $maxValue maximal allowed value
597 * @return mixed[]
599 public static function validateUpperBound(string $path, array $values, int $maxValue): array
601 $result = $values[$path] <= $maxValue;
603 return [
604 $path => $result ? '' : sprintf(
605 __('Value must be less than or equal to %s!'),
606 $maxValue,