<?php /** * Interface to the MySQL Improved extension (MySQLi) */ declare(strict_types=1); namespace PhpMyAdmin\Dbal; use mysqli; use mysqli_stmt; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Query\Utilities; use function __; use function defined; use function mysqli_connect_errno; use function mysqli_connect_error; use function mysqli_get_client_info; use function mysqli_init; use function mysqli_report; use function sprintf; use function stripos; use function trigger_error; use const E_USER_ERROR; use const E_USER_WARNING; use const MYSQLI_CLIENT_COMPRESS; use const MYSQLI_CLIENT_SSL; use const MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; use const MYSQLI_OPT_LOCAL_INFILE; use const MYSQLI_OPT_SSL_VERIFY_SERVER_CERT; use const MYSQLI_REPORT_OFF; use const MYSQLI_STORE_RESULT; use const MYSQLI_USE_RESULT; /** * Interface to the MySQL Improved extension (MySQLi) */ class DbiMysqli implements DbiExtension { /** * connects to the database server * * @param string $user mysql user name * @param string $password mysql user password * @param array $server host/port/socket/persistent * * @return mysqli|bool false on error or a mysqli object on success */ public function connect($user, $password, array $server) { if ($server) { $server['host'] = empty($server['host']) ? 'localhost' : $server['host']; } mysqli_report(MYSQLI_REPORT_OFF); $mysqli = mysqli_init(); if ($mysqli === false) { return false; } $client_flags = 0; /* Optionally compress connection */ if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) { $client_flags |= MYSQLI_CLIENT_COMPRESS; } /* Optionally enable SSL */ if ($server['ssl']) { $client_flags |= MYSQLI_CLIENT_SSL; if ( ! empty($server['ssl_key']) || ! empty($server['ssl_cert']) || ! empty($server['ssl_ca']) || ! empty($server['ssl_ca_path']) || ! empty($server['ssl_ciphers']) ) { $mysqli->ssl_set( $server['ssl_key'] ?? '', $server['ssl_cert'] ?? '', $server['ssl_ca'] ?? '', $server['ssl_ca_path'] ?? '', $server['ssl_ciphers'] ?? '' ); } /* * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later * @link https://bugs.php.net/bug.php?id=68344 * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838 */ if (! $server['ssl_verify']) { $mysqli->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, (int) $server['ssl_verify']); $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; } } if ($GLOBALS['cfg']['PersistentConnections']) { $host = 'p:' . $server['host']; } else { $host = $server['host']; } if ($server['hide_connection_errors']) { $return_value = @$mysqli->real_connect( $host, $user, $password, '', $server['port'], (string) $server['socket'], $client_flags ); } else { $return_value = $mysqli->real_connect( $host, $user, $password, '', $server['port'], (string) $server['socket'], $client_flags ); } if ($return_value === false) { /* * Switch to SSL if server asked us to do so, unfortunately * there are more ways MySQL server can tell this: * * - MySQL 8.0 and newer should return error 3159 * - #2001 - SSL Connection is required. Please specify SSL options and retry. * - #9002 - SSL connection is required. Please specify SSL options and retry. */ // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps $error_number = $mysqli->connect_errno; $error_message = $mysqli->connect_error; // phpcs:enable if ( ! $server['ssl'] && ($error_number == 3159 || (($error_number == 2001 || $error_number == 9002) && stripos($error_message, 'SSL Connection is required') !== false)) ) { trigger_error( __('SSL connection enforced by server, automatically enabling it.'), E_USER_WARNING ); $server['ssl'] = true; return self::connect($user, $password, $server); } if ($error_number === 1045 && $server['hide_connection_errors']) { trigger_error( sprintf( __( 'Error 1045: Access denied for user. Additional error information' . ' may be available, but is being hidden by the %s configuration directive.' ), '[code][doc@cfg_Servers_hide_connection_errors]' . '$cfg[\'Servers\'][$i][\'hide_connection_errors\'][/doc][/code]' ), E_USER_ERROR ); } return false; } $mysqli->options(MYSQLI_OPT_LOCAL_INFILE, (int) defined('PMA_ENABLE_LDI')); return $mysqli; } /** * selects given database * * @param string|DatabaseName $databaseName database name to select * @param mysqli $link the mysqli object */ public function selectDb($databaseName, $link): bool { return $link->select_db((string) $databaseName); } /** * runs a query and returns the result * * @param string $query query to execute * @param mysqli $link mysqli object * @param int $options query options * * @return MysqliResult|false */ public function realQuery(string $query, $link, int $options) { $method = MYSQLI_STORE_RESULT; if ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) { $method = MYSQLI_USE_RESULT; } $result = $link->query($query, $method); if ($result === false) { return false; } return new MysqliResult($result); } /** * Run the multi query and output the results * * @param mysqli $link mysqli object * @param string $query multi query statement to execute */ public function realMultiQuery($link, $query): bool { return $link->multi_query($query); } /** * Check if there are any more query results from a multi query * * @param mysqli $link the mysqli object */ public function moreResults($link): bool { return $link->more_results(); } /** * Prepare next result from multi_query * * @param mysqli $link the mysqli object */ public function nextResult($link): bool { return $link->next_result(); } /** * Store the result returned from multi query * * @param mysqli $link the mysqli object * * @return MysqliResult|false false when empty results / result set when not empty */ public function storeResult($link) { $result = $link->store_result(); return $result === false ? false : new MysqliResult($result); } /** * Returns a string representing the type of connection used * * @param mysqli $link mysql link * * @return string type of connection used */ public function getHostInfo($link) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps return $link->host_info; } /** * Returns the version of the MySQL protocol used * * @param mysqli $link mysql link * * @return string version of the MySQL protocol used */ public function getProtoInfo($link) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps return $link->protocol_version; } /** * returns a string that represents the client library version * * @return string MySQL client library version */ public function getClientInfo() { return mysqli_get_client_info(); } /** * Returns last error message or an empty string if no errors occurred. * * @param mysqli|false|null $link mysql link */ public function getError($link): string { $GLOBALS['errno'] = 0; if ($link !== null && $link !== false) { $error_number = $link->errno; $error_message = $link->error; } else { $error_number = mysqli_connect_errno(); $error_message = (string) mysqli_connect_error(); } if ($error_number === 0 || $error_message === '') { return ''; } // keep the error number for further check after // the call to getError() $GLOBALS['errno'] = $error_number; return Utilities::formatError($error_number, $error_message); } /** * returns the number of rows affected by last query * * @param mysqli $link the mysqli object * * @return int|string * @psalm-return int|numeric-string */ public function affectedRows($link) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps return $link->affected_rows; } /** * returns properly escaped string for use in MySQL queries * * @param mysqli $link database link * @param string $string string to be escaped * * @return string a MySQL escaped string */ public function escapeString($link, $string) { return $link->real_escape_string($string); } /** * Prepare an SQL statement for execution. * * @param mysqli $link database link * @param string $query The query, as a string. * * @return mysqli_stmt|false A statement object or false. */ public function prepare($link, string $query) { return $link->prepare($query); } }