DbiMysqli.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Interface to the improved MySQL extension (MySQLi)
  5. *
  6. * @package PhpMyAdmin-DBI
  7. * @subpackage MySQLi
  8. */
  9. namespace PhpMyAdmin\Dbi;
  10. use PhpMyAdmin\DatabaseInterface;
  11. if (! defined('PHPMYADMIN')) {
  12. exit;
  13. }
  14. /**
  15. * some PHP versions are reporting extra messages like "No index used in query"
  16. */
  17. mysqli_report(MYSQLI_REPORT_OFF);
  18. /**
  19. * some older mysql client libs are missing these constants ...
  20. */
  21. if (! defined('MYSQLI_BINARY_FLAG')) {
  22. define('MYSQLI_BINARY_FLAG', 128);
  23. }
  24. /**
  25. * @see https://bugs.php.net/36007
  26. */
  27. if (! defined('MYSQLI_TYPE_NEWDECIMAL')) {
  28. define('MYSQLI_TYPE_NEWDECIMAL', 246);
  29. }
  30. if (! defined('MYSQLI_TYPE_BIT')) {
  31. define('MYSQLI_TYPE_BIT', 16);
  32. }
  33. if (! defined('MYSQLI_TYPE_JSON')) {
  34. define('MYSQLI_TYPE_JSON', 245);
  35. }
  36. /* vim: set expandtab sw=4 ts=4 sts=4: */
  37. /**
  38. * Interface to the improved MySQL extension (MySQLi)
  39. *
  40. * @package PhpMyAdmin-DBI
  41. * @subpackage MySQLi
  42. */
  43. class DbiMysqli implements DbiExtension
  44. {
  45. static private $pma_mysqli_flag_names = array(
  46. MYSQLI_NUM_FLAG => 'num',
  47. MYSQLI_PART_KEY_FLAG => 'part_key',
  48. MYSQLI_SET_FLAG => 'set',
  49. MYSQLI_TIMESTAMP_FLAG => 'timestamp',
  50. MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment',
  51. MYSQLI_ENUM_FLAG => 'enum',
  52. MYSQLI_ZEROFILL_FLAG => 'zerofill',
  53. MYSQLI_UNSIGNED_FLAG => 'unsigned',
  54. MYSQLI_BLOB_FLAG => 'blob',
  55. MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key',
  56. MYSQLI_UNIQUE_KEY_FLAG => 'unique_key',
  57. MYSQLI_PRI_KEY_FLAG => 'primary_key',
  58. MYSQLI_NOT_NULL_FLAG => 'not_null',
  59. );
  60. /**
  61. * connects to the database server
  62. *
  63. * @param string $user mysql user name
  64. * @param string $password mysql user password
  65. * @param array $server host/port/socket/persistent
  66. *
  67. * @return mixed false on error or a mysqli object on success
  68. */
  69. public function connect(
  70. $user, $password, array $server
  71. ) {
  72. if ($server) {
  73. $server['host'] = (empty($server['host']))
  74. ? 'localhost'
  75. : $server['host'];
  76. }
  77. // NULL enables connection to the default socket
  78. $link = mysqli_init();
  79. $client_flags = 0;
  80. /* Optionally compress connection */
  81. if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) {
  82. $client_flags |= MYSQLI_CLIENT_COMPRESS;
  83. }
  84. /* Optionally enable SSL */
  85. if ($server['ssl']) {
  86. $client_flags |= MYSQLI_CLIENT_SSL;
  87. if (! empty($server['ssl_key']) ||
  88. ! empty($server['ssl_cert']) ||
  89. ! empty($server['ssl_ca']) ||
  90. ! empty($server['ssl_ca_path']) ||
  91. ! empty($server['ssl_ciphers'])
  92. ) {
  93. if (! isset($server['ssl_key']) || is_null($server['ssl_key'])) {
  94. $server['ssl_key'] = '';
  95. }
  96. if (! isset($server['ssl_cert']) || is_null($server['ssl_cert'])) {
  97. $server['ssl_cert'] = '';
  98. }
  99. if (! isset($server['ssl_ca']) || is_null($server['ssl_ca'])) {
  100. $server['ssl_ca'] = '';
  101. }
  102. if (! isset($server['ssl_ca_path']) || is_null($server['ssl_ca_path'])) {
  103. $server['ssl_ca_path'] = '';
  104. }
  105. if (! isset($server['ssl_ciphers']) || is_null($server['ssl_ciphers'])) {
  106. $server['ssl_ciphers'] = '';
  107. }
  108. mysqli_ssl_set(
  109. $link,
  110. $server['ssl_key'],
  111. $server['ssl_cert'],
  112. $server['ssl_ca'],
  113. $server['ssl_ca_path'],
  114. $server['ssl_ciphers']
  115. );
  116. }
  117. /*
  118. * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later
  119. * @link https://bugs.php.net/bug.php?id=68344
  120. * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838
  121. */
  122. if (! $server['ssl_verify']) {
  123. mysqli_options(
  124. $link,
  125. MYSQLI_OPT_SSL_VERIFY_SERVER_CERT,
  126. $server['ssl_verify']
  127. );
  128. $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
  129. }
  130. }
  131. if ($GLOBALS['cfg']['PersistentConnections']) {
  132. $host = 'p:' . $server['host'];
  133. } else {
  134. $host = $server['host'];
  135. }
  136. if ($server['hide_connection_errors']) {
  137. $return_value = @mysqli_real_connect(
  138. $link,
  139. $host,
  140. $user,
  141. $password,
  142. '',
  143. $server['port'],
  144. $server['socket'],
  145. $client_flags
  146. );
  147. } else {
  148. $return_value = mysqli_real_connect(
  149. $link,
  150. $host,
  151. $user,
  152. $password,
  153. '',
  154. $server['port'],
  155. $server['socket'],
  156. $client_flags
  157. );
  158. }
  159. if ($return_value === false || is_null($return_value)) {
  160. /*
  161. * Switch to SSL if server asked us to do so, unfortunately
  162. * there are more ways MySQL server can tell this:
  163. *
  164. * - MySQL 8.0 and newer should return error 3159
  165. * - #2001 - SSL Connection is required. Please specify SSL options and retry.
  166. * - #9002 - SSL connection is required. Please specify SSL options and retry.
  167. */
  168. $error_number = mysqli_connect_errno();
  169. $error_message = mysqli_connect_error();
  170. if (! $server['ssl'] && ($error_number == 3159 ||
  171. (($error_number == 2001 || $error_number == 9002) && stripos($error_message, 'SSL Connection is required') !== false))
  172. ) {
  173. trigger_error(
  174. __('SSL connection enforced by server, automatically enabling it.'),
  175. E_USER_WARNING
  176. );
  177. $server['ssl'] = true;
  178. return self::connect($user, $password, $server);
  179. } elseif ($error_number === 1045 && $server['hide_connection_errors']) {
  180. trigger_error(
  181. sprintf(
  182. __(
  183. 'Error 1045: Access denied for user. Additional error information'
  184. . ' may be available, but is being hidden by the %s configuration directive.'
  185. ),
  186. '[code][doc@cfg_Servers_hide_connection_errors]'
  187. . '$cfg[\'Servers\'][$i][\'hide_connection_errors\'][/doc][/code]'
  188. ),
  189. E_USER_ERROR
  190. );
  191. }
  192. return false;
  193. }
  194. if (defined('PMA_ENABLE_LDI')) {
  195. mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, true);
  196. } else {
  197. mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, false);
  198. }
  199. return $link;
  200. }
  201. /**
  202. * selects given database
  203. *
  204. * @param string $dbname database name to select
  205. * @param mysqli $link the mysqli object
  206. *
  207. * @return boolean
  208. */
  209. public function selectDb($dbname, $link)
  210. {
  211. return mysqli_select_db($link, $dbname);
  212. }
  213. /**
  214. * runs a query and returns the result
  215. *
  216. * @param string $query query to execute
  217. * @param mysqli $link mysqli object
  218. * @param int $options query options
  219. *
  220. * @return mysqli_result|bool
  221. */
  222. public function realQuery($query, $link, $options)
  223. {
  224. if ($options == ($options | DatabaseInterface::QUERY_STORE)) {
  225. $method = MYSQLI_STORE_RESULT;
  226. } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) {
  227. $method = MYSQLI_USE_RESULT;
  228. } else {
  229. $method = 0;
  230. }
  231. return mysqli_query($link, $query, $method);
  232. }
  233. /**
  234. * Run the multi query and output the results
  235. *
  236. * @param mysqli $link mysqli object
  237. * @param string $query multi query statement to execute
  238. *
  239. * @return mysqli_result collection | boolean(false)
  240. */
  241. public function realMultiQuery($link, $query)
  242. {
  243. return mysqli_multi_query($link, $query);
  244. }
  245. /**
  246. * returns array of rows with associative and numeric keys from $result
  247. *
  248. * @param mysqli_result $result result set identifier
  249. *
  250. * @return array
  251. */
  252. public function fetchArray($result)
  253. {
  254. return mysqli_fetch_array($result, MYSQLI_BOTH);
  255. }
  256. /**
  257. * returns array of rows with associative keys from $result
  258. *
  259. * @param mysqli_result $result result set identifier
  260. *
  261. * @return array
  262. */
  263. public function fetchAssoc($result)
  264. {
  265. return mysqli_fetch_array($result, MYSQLI_ASSOC);
  266. }
  267. /**
  268. * returns array of rows with numeric keys from $result
  269. *
  270. * @param mysqli_result $result result set identifier
  271. *
  272. * @return array
  273. */
  274. public function fetchRow($result)
  275. {
  276. return mysqli_fetch_array($result, MYSQLI_NUM);
  277. }
  278. /**
  279. * Adjusts the result pointer to an arbitrary row in the result
  280. *
  281. * @param mysqli_result $result database result
  282. * @param integer $offset offset to seek
  283. *
  284. * @return bool true on success, false on failure
  285. */
  286. public function dataSeek($result, $offset)
  287. {
  288. return mysqli_data_seek($result, $offset);
  289. }
  290. /**
  291. * Frees memory associated with the result
  292. *
  293. * @param mysqli_result $result database result
  294. *
  295. * @return void
  296. */
  297. public function freeResult($result)
  298. {
  299. if ($result instanceof mysqli_result) {
  300. mysqli_free_result($result);
  301. }
  302. }
  303. /**
  304. * Check if there are any more query results from a multi query
  305. *
  306. * @param mysqli $link the mysqli object
  307. *
  308. * @return bool true or false
  309. */
  310. public function moreResults($link)
  311. {
  312. return mysqli_more_results($link);
  313. }
  314. /**
  315. * Prepare next result from multi_query
  316. *
  317. * @param mysqli $link the mysqli object
  318. *
  319. * @return bool true or false
  320. */
  321. public function nextResult($link)
  322. {
  323. return mysqli_next_result($link);
  324. }
  325. /**
  326. * Store the result returned from multi query
  327. *
  328. * @param mysqli $link the mysqli object
  329. *
  330. * @return mixed false when empty results / result set when not empty
  331. */
  332. public function storeResult($link)
  333. {
  334. return mysqli_store_result($link);
  335. }
  336. /**
  337. * Returns a string representing the type of connection used
  338. *
  339. * @param resource $link mysql link
  340. *
  341. * @return string type of connection used
  342. */
  343. public function getHostInfo($link)
  344. {
  345. return mysqli_get_host_info($link);
  346. }
  347. /**
  348. * Returns the version of the MySQL protocol used
  349. *
  350. * @param resource $link mysql link
  351. *
  352. * @return integer version of the MySQL protocol used
  353. */
  354. public function getProtoInfo($link)
  355. {
  356. return mysqli_get_proto_info($link);
  357. }
  358. /**
  359. * returns a string that represents the client library version
  360. *
  361. * @return string MySQL client library version
  362. */
  363. public function getClientInfo()
  364. {
  365. return mysqli_get_client_info();
  366. }
  367. /**
  368. * returns last error message or false if no errors occurred
  369. *
  370. * @param resource $link mysql link
  371. *
  372. * @return string|bool $error or false
  373. */
  374. public function getError($link)
  375. {
  376. $GLOBALS['errno'] = 0;
  377. if (null !== $link && false !== $link) {
  378. $error_number = mysqli_errno($link);
  379. $error_message = mysqli_error($link);
  380. } else {
  381. $error_number = mysqli_connect_errno();
  382. $error_message = mysqli_connect_error();
  383. }
  384. if (0 == $error_number) {
  385. return false;
  386. }
  387. // keep the error number for further check after
  388. // the call to getError()
  389. $GLOBALS['errno'] = $error_number;
  390. return $GLOBALS['dbi']->formatError($error_number, $error_message);
  391. }
  392. /**
  393. * returns the number of rows returned by last query
  394. *
  395. * @param mysqli_result $result result set identifier
  396. *
  397. * @return string|int
  398. */
  399. public function numRows($result)
  400. {
  401. // see the note for tryQuery();
  402. if (is_bool($result)) {
  403. return 0;
  404. }
  405. return @mysqli_num_rows($result);
  406. }
  407. /**
  408. * returns the number of rows affected by last query
  409. *
  410. * @param mysqli $link the mysqli object
  411. *
  412. * @return int
  413. */
  414. public function affectedRows($link)
  415. {
  416. return mysqli_affected_rows($link);
  417. }
  418. /**
  419. * returns metainfo for fields in $result
  420. *
  421. * @param mysqli_result $result result set identifier
  422. *
  423. * @return array meta info for fields in $result
  424. */
  425. public function getFieldsMeta($result)
  426. {
  427. // Build an associative array for a type look up
  428. $typeAr = array();
  429. $typeAr[MYSQLI_TYPE_DECIMAL] = 'real';
  430. $typeAr[MYSQLI_TYPE_NEWDECIMAL] = 'real';
  431. $typeAr[MYSQLI_TYPE_BIT] = 'int';
  432. $typeAr[MYSQLI_TYPE_TINY] = 'int';
  433. $typeAr[MYSQLI_TYPE_SHORT] = 'int';
  434. $typeAr[MYSQLI_TYPE_LONG] = 'int';
  435. $typeAr[MYSQLI_TYPE_FLOAT] = 'real';
  436. $typeAr[MYSQLI_TYPE_DOUBLE] = 'real';
  437. $typeAr[MYSQLI_TYPE_NULL] = 'null';
  438. $typeAr[MYSQLI_TYPE_TIMESTAMP] = 'timestamp';
  439. $typeAr[MYSQLI_TYPE_LONGLONG] = 'int';
  440. $typeAr[MYSQLI_TYPE_INT24] = 'int';
  441. $typeAr[MYSQLI_TYPE_DATE] = 'date';
  442. $typeAr[MYSQLI_TYPE_TIME] = 'time';
  443. $typeAr[MYSQLI_TYPE_DATETIME] = 'datetime';
  444. $typeAr[MYSQLI_TYPE_YEAR] = 'year';
  445. $typeAr[MYSQLI_TYPE_NEWDATE] = 'date';
  446. $typeAr[MYSQLI_TYPE_ENUM] = 'unknown';
  447. $typeAr[MYSQLI_TYPE_SET] = 'unknown';
  448. $typeAr[MYSQLI_TYPE_TINY_BLOB] = 'blob';
  449. $typeAr[MYSQLI_TYPE_MEDIUM_BLOB] = 'blob';
  450. $typeAr[MYSQLI_TYPE_LONG_BLOB] = 'blob';
  451. $typeAr[MYSQLI_TYPE_BLOB] = 'blob';
  452. $typeAr[MYSQLI_TYPE_VAR_STRING] = 'string';
  453. $typeAr[MYSQLI_TYPE_STRING] = 'string';
  454. // MySQL returns MYSQLI_TYPE_STRING for CHAR
  455. // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY
  456. // so this would override TINYINT and mark all TINYINT as string
  457. // see https://github.com/phpmyadmin/phpmyadmin/issues/8569
  458. //$typeAr[MYSQLI_TYPE_CHAR] = 'string';
  459. $typeAr[MYSQLI_TYPE_GEOMETRY] = 'geometry';
  460. $typeAr[MYSQLI_TYPE_BIT] = 'bit';
  461. $typeAr[MYSQLI_TYPE_JSON] = 'json';
  462. $fields = mysqli_fetch_fields($result);
  463. // this happens sometimes (seen under MySQL 4.0.25)
  464. if (!is_array($fields)) {
  465. return false;
  466. }
  467. foreach ($fields as $k => $field) {
  468. $fields[$k]->_type = $field->type;
  469. $fields[$k]->type = $typeAr[$field->type];
  470. $fields[$k]->_flags = $field->flags;
  471. $fields[$k]->flags = $this->fieldFlags($result, $k);
  472. // Enhance the field objects for mysql-extension compatibility
  473. //$flags = explode(' ', $fields[$k]->flags);
  474. //array_unshift($flags, 'dummy');
  475. $fields[$k]->multiple_key
  476. = (int) (bool) ($fields[$k]->_flags & MYSQLI_MULTIPLE_KEY_FLAG);
  477. $fields[$k]->primary_key
  478. = (int) (bool) ($fields[$k]->_flags & MYSQLI_PRI_KEY_FLAG);
  479. $fields[$k]->unique_key
  480. = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNIQUE_KEY_FLAG);
  481. $fields[$k]->not_null
  482. = (int) (bool) ($fields[$k]->_flags & MYSQLI_NOT_NULL_FLAG);
  483. $fields[$k]->unsigned
  484. = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNSIGNED_FLAG);
  485. $fields[$k]->zerofill
  486. = (int) (bool) ($fields[$k]->_flags & MYSQLI_ZEROFILL_FLAG);
  487. $fields[$k]->numeric
  488. = (int) (bool) ($fields[$k]->_flags & MYSQLI_NUM_FLAG);
  489. $fields[$k]->blob
  490. = (int) (bool) ($fields[$k]->_flags & MYSQLI_BLOB_FLAG);
  491. }
  492. return $fields;
  493. }
  494. /**
  495. * return number of fields in given $result
  496. *
  497. * @param mysqli_result $result result set identifier
  498. *
  499. * @return int field count
  500. */
  501. public function numFields($result)
  502. {
  503. return mysqli_num_fields($result);
  504. }
  505. /**
  506. * returns the length of the given field $i in $result
  507. *
  508. * @param mysqli_result $result result set identifier
  509. * @param int $i field
  510. *
  511. * @return int length of field
  512. */
  513. public function fieldLen($result, $i)
  514. {
  515. if ($i >= $this->numFields($result)) {
  516. return false;
  517. }
  518. return mysqli_fetch_field_direct($result, $i)->length;
  519. }
  520. /**
  521. * returns name of $i. field in $result
  522. *
  523. * @param mysqli_result $result result set identifier
  524. * @param int $i field
  525. *
  526. * @return string name of $i. field in $result
  527. */
  528. public function fieldName($result, $i)
  529. {
  530. if ($i >= $this->numFields($result)) {
  531. return false;
  532. }
  533. return mysqli_fetch_field_direct($result, $i)->name;
  534. }
  535. /**
  536. * returns concatenated string of human readable field flags
  537. *
  538. * @param mysqli_result $result result set identifier
  539. * @param int $i field
  540. *
  541. * @return string field flags
  542. */
  543. public function fieldFlags($result, $i)
  544. {
  545. if ($i >= $this->numFields($result)) {
  546. return false;
  547. }
  548. $f = mysqli_fetch_field_direct($result, $i);
  549. $type = $f->type;
  550. $charsetnr = $f->charsetnr;
  551. $f = $f->flags;
  552. $flags = array();
  553. foreach (self::$pma_mysqli_flag_names as $flag => $name) {
  554. if ($f & $flag) {
  555. $flags[] = $name;
  556. }
  557. }
  558. // See https://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html:
  559. // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG
  560. // but instead the charsetnr member of the MYSQL_FIELD
  561. // structure. Watch out: some types like DATE returns 63 in charsetnr
  562. // so we have to check also the type.
  563. // Unfortunately there is no equivalent in the mysql extension.
  564. if (($type == MYSQLI_TYPE_TINY_BLOB || $type == MYSQLI_TYPE_BLOB
  565. || $type == MYSQLI_TYPE_MEDIUM_BLOB || $type == MYSQLI_TYPE_LONG_BLOB
  566. || $type == MYSQLI_TYPE_VAR_STRING || $type == MYSQLI_TYPE_STRING)
  567. && 63 == $charsetnr
  568. ) {
  569. $flags[] = 'binary';
  570. }
  571. return implode(' ', $flags);
  572. }
  573. /**
  574. * returns properly escaped string for use in MySQL queries
  575. *
  576. * @param mixed $link database link
  577. * @param string $str string to be escaped
  578. *
  579. * @return string a MySQL escaped string
  580. */
  581. public function escapeString($link, $str)
  582. {
  583. return mysqli_real_escape_string($link, $str);
  584. }
  585. }