3 * Form validation for configuration editor
6 declare(strict_types
=1);
8 namespace PhpMyAdmin\Config
;
10 use PhpMyAdmin\Config
;
12 use PhpMyAdmin\Sanitize
;
16 use function array_map
;
17 use function array_merge
;
18 use function array_shift
;
19 use function call_user_func_array
;
21 use function error_clear_last
;
22 use function error_get_last
;
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
;
36 use function str_replace
;
37 use function str_starts_with
;
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
;
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).
60 * Returns validator list
62 * @param ConfigFile $cf Config file instance
66 public static function getValidators(ConfigFile
$cf): array
68 static $validators = null;
70 if ($validators !== null) {
74 $validators = $cf->getDbEntry('_validators', []);
75 $config = Config
::getInstance();
76 if ($config->get('is_setup')) {
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)) {
92 for ($i = 1, $nb = count($uv); $i < $nb; $i++
) {
93 if (! str_starts_with($uv[$i], 'value:')) {
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)
113 * Runs validation $validator_id on values $values and returns error list.
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
127 public static function validate(
129 string|
array $validatorId,
134 $validatorId = (array) $validatorId;
135 $validators = static::getValidators($cf);
137 foreach ($validatorId as &$vid) {
138 $vid = $cf->getCanonicalPath($vid);
139 if (! isset($validators[$vid])) {
150 // create argument list with canonical paths and remember path mapping
153 foreach ($values as $k => $v) {
154 $k2 = $isPostSource ?
str_replace('-', '/', $k) : $k;
155 $k2 = mb_strpos($k2, '/')
156 ?
$cf->getCanonicalPath($k2)
159 $arguments[$k2] = $v;
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);
175 if (! is_array($r)) {
179 foreach ($r as $key => $errorList) {
180 // skip empty values if $isPostSource is false
181 if (! $isPostSource && empty($errorList)) {
185 if (! isset($result[$key])) {
189 $errorList = array_map(Sanitize
::convertBBCode(...), (array) $errorList);
190 $result[$key] = array_merge($result[$key], $errorList);
195 // restore original paths
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(
221 string $errorKey = 'Server',
223 $config = Config
::getInstance();
224 if ($config->config
->debug
->demo
) {
225 // Connection test disabled on the demo server!
230 $host = Core
::sanitizeMySQLHost($host);
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);
243 $error = __('Could not connect to the database server!');
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
268 public static function validateServer(string $path, array $values): array
272 'Servers/1/user' => '',
273 'Servers/1/SignonSession' => '',
274 'Servers/1/SignonURL' => '',
277 if (empty($values['Servers/1/auth_type'])) {
278 $values['Servers/1/auth_type'] = '';
279 $result['Servers/1/auth_type'] = __('Invalid authentication type!');
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!');
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!',
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!',
302 if (! $error && $values['Servers/1/auth_type'] === 'config') {
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'],
316 if (is_array($test)) {
317 $result = array_merge($result, $test);
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
334 public static function validatePMAStorage(string $path, array $values): array
337 'Server_pmadb' => '',
338 'Servers/1/controluser' => '',
339 'Servers/1/controlpass' => '',
343 if (empty($values['Servers/1/pmadb'])) {
348 if (empty($values['Servers/1/controluser'])) {
349 $result['Servers/1/controluser'] = __(
350 'Empty phpMyAdmin control user while using phpMyAdmin configuration storage!',
355 if (empty($values['Servers/1/controlpass'])) {
356 $result['Servers/1/controlpass'] = __(
357 'Empty phpMyAdmin control user password while using phpMyAdmin configuration storage!',
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'],
371 if (is_array($test)) {
372 $result = array_merge($result, $test);
380 * Validates regular expression
382 * @param string $path path to config
383 * @param mixed[] $values config values
387 public static function validateRegex(string $path, array $values): array
389 $result = [$path => ''];
391 if (empty($values[$path])) {
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];
414 * Validates TrustedProxies field
416 * @param string $path path to config
417 * @param mixed[] $values config values
421 public static function validateTrustedProxies(string $path, array $values): array
423 $result = [$path => []];
425 if (empty($values[$path])) {
429 if (is_array($values[$path]) ||
is_object($values[$path])) {
430 // value already processed by FormDisplay::save
432 foreach ($values[$path] as $ip => $v) {
433 $v = Util
::requestString($v);
434 $lines[] = preg_match('/^-\d+$/', $ip)
440 $lines = explode("\n", $values[$path]);
443 foreach ($lines as $line) {
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);
453 // now let's check whether we really have an IP address
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);
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(
487 if (empty($values[$path])) {
491 $value = Util
::requestString($values[$path]);
494 (int) $value != $value
495 ||
(! $allowNegative && $value < 0)
496 ||
(! $allowZero && $value == 0)
497 ||
$value > $maxValue
506 * Validates port number
508 * @param string $path path to config
509 * @param mixed[] $values config values
513 public static function validatePortNumber(string $path, array $values): array
516 $path => static::validateNumber(
522 __('Not a valid port number!'),
528 * Validates positive number
530 * @param string $path path to config
531 * @param mixed[] $values config values
535 public static function validatePositiveNumber(string $path, array $values): array
538 $path => static::validateNumber(
544 __('Not a positive number!'),
550 * Validates non-negative number
552 * @param string $path path to config
553 * @param mixed[] $values config values
557 public static function validateNonNegativeNumber(string $path, array $values): array
560 $path => static::validateNumber(
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])) {
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
599 public static function validateUpperBound(string $path, array $values, int $maxValue): array
601 $result = $values[$path] <= $maxValue;
604 $path => $result ?
'' : sprintf(
605 __('Value must be less than or equal to %s!'),