diff options
Diffstat (limited to 'srcs/phpmyadmin/libraries/classes/Config/Validator.php')
| -rw-r--r-- | srcs/phpmyadmin/libraries/classes/Config/Validator.php | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/libraries/classes/Config/Validator.php b/srcs/phpmyadmin/libraries/classes/Config/Validator.php new file mode 100644 index 0000000..8294a90 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Validator.php @@ -0,0 +1,594 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Form validation for configuration editor + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config\ConfigFile; +use PhpMyAdmin\Core; +use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Util; +use function mysql_close; +use function mysql_connect; +use function mysqli_close; +use function mysqli_connect; + +/** + * Validation class for various validation functions + * + * Validation function takes two argument: id for which it is called + * and array of fields' values (usually values for entire formset, as defined + * in forms.inc.php). + * The function must always return an array with an error (or error array) + * assigned to a form element (formset name or field path). Even if there are + * no errors, key must be set with an empty value. + * + * Validation functions are assigned in $cfg_db['_validators'] (config.values.php). + * + * @package PhpMyAdmin + */ +class Validator +{ + /** + * Returns validator list + * + * @param ConfigFile $cf Config file instance + * + * @return array + */ + public static function getValidators(ConfigFile $cf) + { + static $validators = null; + + if ($validators !== null) { + return $validators; + } + + $validators = $cf->getDbEntry('_validators', []); + if ($GLOBALS['PMA_Config']->get('is_setup')) { + return $validators; + } + + // not in setup script: load additional validators for user + // preferences we need original config values not overwritten + // by user preferences, creating a new PhpMyAdmin\Config instance is a + // better idea than hacking into its code + $uvs = $cf->getDbEntry('_userValidators', []); + foreach ($uvs as $field => $uvList) { + $uvList = (array) $uvList; + foreach ($uvList as &$uv) { + if (! is_array($uv)) { + continue; + } + for ($i = 1, $nb = count($uv); $i < $nb; $i++) { + if (mb_substr($uv[$i], 0, 6) == 'value:') { + $uv[$i] = Core::arrayRead( + mb_substr($uv[$i], 6), + $GLOBALS['PMA_Config']->base_settings + ); + } + } + } + $validators[$field] = isset($validators[$field]) + ? array_merge((array) $validators[$field], $uvList) + : $uvList; + } + return $validators; + } + + /** + * Runs validation $validator_id on values $values and returns error list. + * + * Return values: + * o array, keys - field path or formset id, values - array of errors + * when $isPostSource is true values is an empty array to allow for error list + * cleanup in HTML document + * o false - when no validators match name(s) given by $validator_id + * + * @param ConfigFile $cf Config file instance + * @param string|array $validatorId ID of validator(s) to run + * @param array $values Values to validate + * @param bool $isPostSource tells whether $values are directly from + * POST request + * + * @return bool|array + */ + public static function validate( + ConfigFile $cf, + $validatorId, + array &$values, + $isPostSource + ) { + // find validators + $validatorId = (array) $validatorId; + $validators = static::getValidators($cf); + $vids = []; + foreach ($validatorId as &$vid) { + $vid = $cf->getCanonicalPath($vid); + if (isset($validators[$vid])) { + $vids[] = $vid; + } + } + if (empty($vids)) { + return false; + } + + // create argument list with canonical paths and remember path mapping + $arguments = []; + $keyMap = []; + foreach ($values as $k => $v) { + $k2 = $isPostSource ? str_replace('-', '/', $k) : $k; + $k2 = mb_strpos($k2, '/') + ? $cf->getCanonicalPath($k2) + : $k2; + $keyMap[$k2] = $k; + $arguments[$k2] = $v; + } + + // validate + $result = []; + foreach ($vids as $vid) { + // call appropriate validation functions + foreach ((array) $validators[$vid] as $validator) { + $vdef = (array) $validator; + $vname = array_shift($vdef); + $vname = 'PhpMyAdmin\Config\Validator::' . $vname; + $args = array_merge([$vid, &$arguments], $vdef); + $r = call_user_func_array($vname, $args); + + // merge results + if (! is_array($r)) { + continue; + } + + foreach ($r as $key => $errorList) { + // skip empty values if $isPostSource is false + if (! $isPostSource && empty($errorList)) { + continue; + } + if (! isset($result[$key])) { + $result[$key] = []; + } + $result[$key] = array_merge( + $result[$key], + (array) $errorList + ); + } + } + } + + // restore original paths + $newResult = []; + foreach ($result as $k => $v) { + $k2 = isset($keyMap[$k]) ? $keyMap[$k] : $k; + if (is_array($v)) { + $newResult[$k2] = array_map('htmlspecialchars', $v); + } else { + $newResult[$k2] = htmlspecialchars($v); + } + } + return empty($newResult) ? true : $newResult; + } + + /** + * Test database connection + * + * @param string $host host name + * @param string $port tcp port to use + * @param string $socket socket to use + * @param string $user username to use + * @param string $pass password to use + * @param string $errorKey key to use in return array + * + * @return bool|array + */ + public static function testDBConnection( + $host, + $port, + $socket, + $user, + $pass = null, + $errorKey = 'Server' + ) { + if ($GLOBALS['cfg']['DBG']['demo']) { + // Connection test disabled on the demo server! + return true; + } + + $error = null; + $host = Core::sanitizeMySQLHost($host); + + error_clear_last(); + + if (DatabaseInterface::checkDbExtension('mysqli')) { + $socket = empty($socket) ? null : $socket; + $port = empty($port) ? null : $port; + $extension = 'mysqli'; + } else { + $socket = empty($socket) ? null : ':' . ($socket[0] == '/' ? '' : '/') . $socket; + $port = empty($port) ? null : ':' . $port; + $extension = 'mysql'; + } + + if ($extension == 'mysql') { + $conn = @mysql_connect($host . $port . $socket, $user, $pass); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysql_close($conn); + } + } else { + $conn = @mysqli_connect($host, $user, $pass, null, $port, $socket); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysqli_close($conn); + } + } + if ($error !== null) { + $lastError = error_get_last(); + if ($lastError !== null) { + $error .= ' - ' . $lastError['message']; + } + } + return $error === null ? true : [$errorKey => $error]; + } + + /** + * Validate server config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validateServer($path, array $values) + { + $result = [ + 'Server' => '', + 'Servers/1/user' => '', + 'Servers/1/SignonSession' => '', + 'Servers/1/SignonURL' => '', + ]; + $error = false; + if (empty($values['Servers/1/auth_type'])) { + $values['Servers/1/auth_type'] = ''; + $result['Servers/1/auth_type'] = __('Invalid authentication type!'); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'config' + && empty($values['Servers/1/user']) + ) { + $result['Servers/1/user'] = __( + 'Empty username while using [kbd]config[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonSession']) + ) { + $result['Servers/1/SignonSession'] = __( + 'Empty signon session name ' + . 'while using [kbd]signon[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonURL']) + ) { + $result['Servers/1/SignonURL'] = __( + 'Empty signon URL while using [kbd]signon[/kbd] authentication ' + . 'method!' + ); + $error = true; + } + + if (! $error && $values['Servers/1/auth_type'] == 'config') { + $password = ''; + if (! empty($values['Servers/1/password'])) { + $password = $values['Servers/1/password']; + } + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/user']) ? '' : $values['Servers/1/user'], + $password, + 'Server' + ); + + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + /** + * Validate pmadb config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validatePMAStorage($path, array $values) + { + $result = [ + 'Server_pmadb' => '', + 'Servers/1/controluser' => '', + 'Servers/1/controlpass' => '', + ]; + $error = false; + + if (empty($values['Servers/1/pmadb'])) { + return $result; + } + + $result = []; + if (empty($values['Servers/1/controluser'])) { + $result['Servers/1/controluser'] = __( + 'Empty phpMyAdmin control user while using phpMyAdmin configuration ' + . 'storage!' + ); + $error = true; + } + if (empty($values['Servers/1/controlpass'])) { + $result['Servers/1/controlpass'] = __( + 'Empty phpMyAdmin control user password while using phpMyAdmin ' + . 'configuration storage!' + ); + $error = true; + } + if (! $error) { + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/controluser']) ? '' : $values['Servers/1/controluser'], + empty($values['Servers/1/controlpass']) ? '' : $values['Servers/1/controlpass'], + 'Server_pmadb' + ); + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + + /** + * Validates regular expression + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateRegex($path, array $values) + { + $result = [$path => '']; + + if (empty($values[$path])) { + return $result; + } + + error_clear_last(); + + $matches = []; + // in libraries/ListDatabase.php _checkHideDatabase(), + // a '/' is used as the delimiter for hide_db + @preg_match('/' . Util::requestString($values[$path]) . '/', '', $matches); + + $currentError = error_get_last(); + + if ($currentError !== null) { + $error = preg_replace('/^preg_match\(\): /', '', $currentError['message']); + return [$path => $error]; + } + + return $result; + } + + /** + * Validates TrustedProxies field + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateTrustedProxies($path, array $values) + { + $result = [$path => []]; + + if (empty($values[$path])) { + return $result; + } + + if (is_array($values[$path]) || is_object($values[$path])) { + // value already processed by FormDisplay::save + $lines = []; + foreach ($values[$path] as $ip => $v) { + $v = Util::requestString($v); + $lines[] = preg_match('/^-\d+$/', $ip) + ? $v + : $ip . ': ' . $v; + } + } else { + // AJAX validation + $lines = explode("\n", $values[$path]); + } + foreach ($lines as $line) { + $line = trim($line); + $matches = []; + // we catch anything that may (or may not) be an IP + if (! preg_match("/^(.+):(?:[ ]?)\\w+$/", $line, $matches)) { + $result[$path][] = __('Incorrect value:') . ' ' + . htmlspecialchars($line); + continue; + } + // now let's check whether we really have an IP address + if (filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false + && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false + ) { + $ip = htmlspecialchars(trim($matches[1])); + $result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip); + continue; + } + } + + return $result; + } + + /** + * Tests integer value + * + * @param string $path path to config + * @param array $values config values + * @param bool $allowNegative allow negative values + * @param bool $allowZero allow zero + * @param int $maxValue max allowed value + * @param string $errorString error message string + * + * @return string empty string if test is successful + */ + public static function validateNumber( + $path, + array $values, + $allowNegative, + $allowZero, + $maxValue, + $errorString + ) { + if (empty($values[$path])) { + return ''; + } + + $value = Util::requestString($values[$path]); + + if (intval($value) != $value + || (! $allowNegative && $value < 0) + || (! $allowZero && $value == 0) + || $value > $maxValue + ) { + return $errorString; + } + + return ''; + } + + /** + * Validates port number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePortNumber($path, array $values) + { + return [ + $path => static::validateNumber( + $path, + $values, + false, + false, + 65535, + __('Not a valid port number!') + ), + ]; + } + + /** + * Validates positive number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePositiveNumber($path, array $values) + { + return [ + $path => static::validateNumber( + $path, + $values, + false, + false, + PHP_INT_MAX, + __('Not a positive number!') + ), + ]; + } + + /** + * Validates non-negative number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateNonNegativeNumber($path, array $values) + { + return [ + $path => static::validateNumber( + $path, + $values, + false, + true, + PHP_INT_MAX, + __('Not a non-negative number!') + ), + ]; + } + + /** + * Validates value according to given regular expression + * Pattern and modifiers must be a valid for PCRE <b>and</b> JavaScript RegExp + * + * @param string $path path to config + * @param array $values config values + * @param string $regex regular expression to match + * + * @return array|string + */ + public static function validateByRegex($path, array $values, $regex) + { + if (! isset($values[$path])) { + return ''; + } + $result = preg_match($regex, Util::requestString($values[$path])); + return [$path => $result ? '' : __('Incorrect value!')]; + } + + /** + * Validates upper bound for numeric inputs + * + * @param string $path path to config + * @param array $values config values + * @param int $maxValue maximal allowed value + * + * @return array + */ + public static function validateUpperBound($path, array $values, $maxValue) + { + $result = $values[$path] <= $maxValue; + return [ + $path => $result ? '' : sprintf( + __('Value must be less than or equal to %s!'), + $maxValue + ), + ]; + } +} |
