Core.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Core functions used all over the scripts.
  5. * This script is distinct from libraries/common.inc.php because this
  6. * script is called from /test.
  7. *
  8. * @package PhpMyAdmin
  9. */
  10. namespace PhpMyAdmin;
  11. use PhpMyAdmin\DatabaseInterface;
  12. use PhpMyAdmin\Message;
  13. use PhpMyAdmin\Response;
  14. use PhpMyAdmin\Sanitize;
  15. use PhpMyAdmin\Template;
  16. use PhpMyAdmin\Url;
  17. use PhpMyAdmin\Util;
  18. /**
  19. * Core class
  20. *
  21. * @package PhpMyAdmin
  22. */
  23. class Core
  24. {
  25. /**
  26. * the whitelist for goto parameter
  27. * @static array $goto_whitelist
  28. */
  29. public static $goto_whitelist = array(
  30. 'db_datadict.php',
  31. 'db_sql.php',
  32. 'db_events.php',
  33. 'db_export.php',
  34. 'db_importdocsql.php',
  35. 'db_multi_table_query.php',
  36. 'db_structure.php',
  37. 'db_import.php',
  38. 'db_operations.php',
  39. 'db_search.php',
  40. 'db_routines.php',
  41. 'export.php',
  42. 'import.php',
  43. 'index.php',
  44. 'pdf_pages.php',
  45. 'pdf_schema.php',
  46. 'server_binlog.php',
  47. 'server_collations.php',
  48. 'server_databases.php',
  49. 'server_engines.php',
  50. 'server_export.php',
  51. 'server_import.php',
  52. 'server_privileges.php',
  53. 'server_sql.php',
  54. 'server_status.php',
  55. 'server_status_advisor.php',
  56. 'server_status_monitor.php',
  57. 'server_status_queries.php',
  58. 'server_status_variables.php',
  59. 'server_variables.php',
  60. 'sql.php',
  61. 'tbl_addfield.php',
  62. 'tbl_change.php',
  63. 'tbl_create.php',
  64. 'tbl_import.php',
  65. 'tbl_indexes.php',
  66. 'tbl_sql.php',
  67. 'tbl_export.php',
  68. 'tbl_operations.php',
  69. 'tbl_structure.php',
  70. 'tbl_relation.php',
  71. 'tbl_replace.php',
  72. 'tbl_row_action.php',
  73. 'tbl_select.php',
  74. 'tbl_zoom_select.php',
  75. 'transformation_overview.php',
  76. 'transformation_wrapper.php',
  77. 'user_password.php',
  78. );
  79. /**
  80. * checks given $var and returns it if valid, or $default of not valid
  81. * given $var is also checked for type being 'similar' as $default
  82. * or against any other type if $type is provided
  83. *
  84. * <code>
  85. * // $_REQUEST['db'] not set
  86. * echo Core::ifSetOr($_REQUEST['db'], ''); // ''
  87. * // $_POST['sql_query'] not set
  88. * echo Core::ifSetOr($_POST['sql_query']); // null
  89. * // $cfg['EnableFoo'] not set
  90. * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false
  91. * echo Core::ifSetOr($cfg['EnableFoo']); // null
  92. * // $cfg['EnableFoo'] set to 1
  93. * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false
  94. * echo Core::ifSetOr($cfg['EnableFoo'], false, 'similar'); // 1
  95. * echo Core::ifSetOr($cfg['EnableFoo'], false); // 1
  96. * // $cfg['EnableFoo'] set to true
  97. * echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // true
  98. * </code>
  99. *
  100. * @param mixed &$var param to check
  101. * @param mixed $default default value
  102. * @param mixed $type var type or array of values to check against $var
  103. *
  104. * @return mixed $var or $default
  105. *
  106. * @see self::isValid()
  107. */
  108. public static function ifSetOr(&$var, $default = null, $type = 'similar')
  109. {
  110. if (! self::isValid($var, $type, $default)) {
  111. return $default;
  112. }
  113. return $var;
  114. }
  115. /**
  116. * checks given $var against $type or $compare
  117. *
  118. * $type can be:
  119. * - false : no type checking
  120. * - 'scalar' : whether type of $var is integer, float, string or boolean
  121. * - 'numeric' : whether type of $var is any number representation
  122. * - 'length' : whether type of $var is scalar with a string length > 0
  123. * - 'similar' : whether type of $var is similar to type of $compare
  124. * - 'equal' : whether type of $var is identical to type of $compare
  125. * - 'identical' : whether $var is identical to $compare, not only the type!
  126. * - or any other valid PHP variable type
  127. *
  128. * <code>
  129. * // $_REQUEST['doit'] = true;
  130. * Core::isValid($_REQUEST['doit'], 'identical', 'true'); // false
  131. * // $_REQUEST['doit'] = 'true';
  132. * Core::isValid($_REQUEST['doit'], 'identical', 'true'); // true
  133. * </code>
  134. *
  135. * NOTE: call-by-reference is used to not get NOTICE on undefined vars,
  136. * but the var is not altered inside this function, also after checking a var
  137. * this var exists nut is not set, example:
  138. * <code>
  139. * // $var is not set
  140. * isset($var); // false
  141. * functionCallByReference($var); // false
  142. * isset($var); // true
  143. * functionCallByReference($var); // true
  144. * </code>
  145. *
  146. * to avoid this we set this var to null if not isset
  147. *
  148. * @param mixed &$var variable to check
  149. * @param mixed $type var type or array of valid values to check against $var
  150. * @param mixed $compare var to compare with $var
  151. *
  152. * @return boolean whether valid or not
  153. *
  154. * @todo add some more var types like hex, bin, ...?
  155. * @see https://secure.php.net/gettype
  156. */
  157. public static function isValid(&$var, $type = 'length', $compare = null)
  158. {
  159. if (! isset($var)) {
  160. // var is not even set
  161. return false;
  162. }
  163. if ($type === false) {
  164. // no vartype requested
  165. return true;
  166. }
  167. if (is_array($type)) {
  168. return in_array($var, $type);
  169. }
  170. // allow some aliases of var types
  171. $type = strtolower($type);
  172. switch ($type) {
  173. case 'identic' :
  174. $type = 'identical';
  175. break;
  176. case 'len' :
  177. $type = 'length';
  178. break;
  179. case 'bool' :
  180. $type = 'boolean';
  181. break;
  182. case 'float' :
  183. $type = 'double';
  184. break;
  185. case 'int' :
  186. $type = 'integer';
  187. break;
  188. case 'null' :
  189. $type = 'NULL';
  190. break;
  191. }
  192. if ($type === 'identical') {
  193. return $var === $compare;
  194. }
  195. // whether we should check against given $compare
  196. if ($type === 'similar') {
  197. switch (gettype($compare)) {
  198. case 'string':
  199. case 'boolean':
  200. $type = 'scalar';
  201. break;
  202. case 'integer':
  203. case 'double':
  204. $type = 'numeric';
  205. break;
  206. default:
  207. $type = gettype($compare);
  208. }
  209. } elseif ($type === 'equal') {
  210. $type = gettype($compare);
  211. }
  212. // do the check
  213. if ($type === 'length' || $type === 'scalar') {
  214. $is_scalar = is_scalar($var);
  215. if ($is_scalar && $type === 'length') {
  216. return strlen($var) > 0;
  217. }
  218. return $is_scalar;
  219. }
  220. if ($type === 'numeric') {
  221. return is_numeric($var);
  222. }
  223. return gettype($var) === $type;
  224. }
  225. /**
  226. * Removes insecure parts in a path; used before include() or
  227. * require() when a part of the path comes from an insecure source
  228. * like a cookie or form.
  229. *
  230. * @param string $path The path to check
  231. *
  232. * @return string The secured path
  233. *
  234. * @access public
  235. */
  236. public static function securePath($path)
  237. {
  238. // change .. to .
  239. $path = preg_replace('@\.\.*@', '.', $path);
  240. return $path;
  241. } // end function
  242. /**
  243. * displays the given error message on phpMyAdmin error page in foreign language,
  244. * ends script execution and closes session
  245. *
  246. * loads language file if not loaded already
  247. *
  248. * @param string $error_message the error message or named error message
  249. * @param string|array $message_args arguments applied to $error_message
  250. *
  251. * @return void
  252. */
  253. public static function fatalError($error_message, $message_args = null) {
  254. /* Use format string if applicable */
  255. if (is_string($message_args)) {
  256. $error_message = sprintf($error_message, $message_args);
  257. } elseif (is_array($message_args)) {
  258. $error_message = vsprintf($error_message, $message_args);
  259. }
  260. /*
  261. * Avoid using Response class as config does not have to be loaded yet
  262. * (this can happen on early fatal error)
  263. */
  264. if (!is_null($GLOBALS['dbi']) && isset($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup') === false && Response::getInstance()->isAjax()) {
  265. $response = Response::getInstance();
  266. $response->setRequestStatus(false);
  267. $response->addJSON('message', Message::error($error_message));
  268. } elseif (! empty($_REQUEST['ajax_request'])) {
  269. // Generate JSON manually
  270. self::headerJSON();
  271. echo json_encode(
  272. array(
  273. 'success' => false,
  274. 'message' => Message::error($error_message)->getDisplay(),
  275. )
  276. );
  277. } else {
  278. $error_message = strtr($error_message, array('<br />' => '[br]'));
  279. $error_header = __('Error');
  280. $lang = isset($GLOBALS['lang']) ? $GLOBALS['lang'] : 'en';
  281. $dir = isset($GLOBALS['text_dir']) ? $GLOBALS['text_dir'] : 'ltr';
  282. // Displays the error message
  283. include './libraries/error.inc.php';
  284. }
  285. if (! defined('TESTSUITE')) {
  286. exit;
  287. }
  288. }
  289. /**
  290. * Returns a link to the PHP documentation
  291. *
  292. * @param string $target anchor in documentation
  293. *
  294. * @return string the URL
  295. *
  296. * @access public
  297. */
  298. public static function getPHPDocLink($target)
  299. {
  300. /* List of PHP documentation translations */
  301. $php_doc_languages = array(
  302. 'pt_BR', 'zh', 'fr', 'de', 'it', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr'
  303. );
  304. $lang = 'en';
  305. if (in_array($GLOBALS['lang'], $php_doc_languages)) {
  306. $lang = $GLOBALS['lang'];
  307. }
  308. return self::linkURL('https://secure.php.net/manual/' . $lang . '/' . $target);
  309. }
  310. /**
  311. * Warn or fail on missing extension.
  312. *
  313. * @param string $extension Extension name
  314. * @param bool $fatal Whether the error is fatal.
  315. * @param string $extra Extra string to append to message.
  316. *
  317. * @return void
  318. */
  319. public static function warnMissingExtension($extension, $fatal = false, $extra = '')
  320. {
  321. /* Gettext does not have to be loaded yet here */
  322. if (function_exists('__')) {
  323. $message = __(
  324. 'The %s extension is missing. Please check your PHP configuration.'
  325. );
  326. } else {
  327. $message
  328. = 'The %s extension is missing. Please check your PHP configuration.';
  329. }
  330. $doclink = self::getPHPDocLink('book.' . $extension . '.php');
  331. $message = sprintf(
  332. $message,
  333. '[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]'
  334. );
  335. if ($extra != '') {
  336. $message .= ' ' . $extra;
  337. }
  338. if ($fatal) {
  339. self::fatalError($message);
  340. return;
  341. }
  342. $GLOBALS['error_handler']->addError(
  343. $message,
  344. E_USER_WARNING,
  345. '',
  346. '',
  347. false
  348. );
  349. }
  350. /**
  351. * returns count of tables in given db
  352. *
  353. * @param string $db database to count tables for
  354. *
  355. * @return integer count of tables in $db
  356. */
  357. public static function getTableCount($db)
  358. {
  359. $tables = $GLOBALS['dbi']->tryQuery(
  360. 'SHOW TABLES FROM ' . Util::backquote($db) . ';',
  361. DatabaseInterface::CONNECT_USER,
  362. DatabaseInterface::QUERY_STORE
  363. );
  364. if ($tables) {
  365. $num_tables = $GLOBALS['dbi']->numRows($tables);
  366. $GLOBALS['dbi']->freeResult($tables);
  367. } else {
  368. $num_tables = 0;
  369. }
  370. return $num_tables;
  371. }
  372. /**
  373. * Converts numbers like 10M into bytes
  374. * Used with permission from Moodle (https://moodle.org) by Martin Dougiamas
  375. * (renamed with PMA prefix to avoid double definition when embedded
  376. * in Moodle)
  377. *
  378. * @param string|int $size size (Default = 0)
  379. *
  380. * @return integer $size
  381. */
  382. public static function getRealSize($size = 0)
  383. {
  384. if (! $size) {
  385. return 0;
  386. }
  387. $binaryprefixes = array(
  388. 'T' => 1099511627776,
  389. 't' => 1099511627776,
  390. 'G' => 1073741824,
  391. 'g' => 1073741824,
  392. 'M' => 1048576,
  393. 'm' => 1048576,
  394. 'K' => 1024,
  395. 'k' => 1024,
  396. );
  397. if (preg_match('/^([0-9]+)([KMGT])/i', $size, $matches)) {
  398. return $matches[1] * $binaryprefixes[$matches[2]];
  399. }
  400. return (int) $size;
  401. } // end getRealSize()
  402. /**
  403. * boolean phpMyAdmin.Core::checkPageValidity(string &$page, array $whitelist)
  404. *
  405. * checks given $page against given $whitelist and returns true if valid
  406. * it optionally ignores query parameters in $page (script.php?ignored)
  407. *
  408. * @param string &$page page to check
  409. * @param array $whitelist whitelist to check page against
  410. * @param boolean $include whether the page is going to be included
  411. *
  412. * @return boolean whether $page is valid or not (in $whitelist or not)
  413. */
  414. public static function checkPageValidity(&$page, array $whitelist = [], $include = false)
  415. {
  416. if (empty($whitelist)) {
  417. $whitelist = self::$goto_whitelist;
  418. }
  419. if (! isset($page) || !is_string($page)) {
  420. return false;
  421. }
  422. if (in_array($page, $whitelist)) {
  423. return true;
  424. }
  425. if ($include) {
  426. return false;
  427. }
  428. $_page = mb_substr(
  429. $page,
  430. 0,
  431. mb_strpos($page . '?', '?')
  432. );
  433. if (in_array($_page, $whitelist)) {
  434. return true;
  435. }
  436. $_page = urldecode($page);
  437. $_page = mb_substr(
  438. $_page,
  439. 0,
  440. mb_strpos($_page . '?', '?')
  441. );
  442. if (in_array($_page, $whitelist)) {
  443. return true;
  444. }
  445. return false;
  446. }
  447. /**
  448. * tries to find the value for the given environment variable name
  449. *
  450. * searches in $_SERVER, $_ENV then tries getenv() and apache_getenv()
  451. * in this order
  452. *
  453. * @param string $var_name variable name
  454. *
  455. * @return string value of $var or empty string
  456. */
  457. public static function getenv($var_name)
  458. {
  459. if (isset($_SERVER[$var_name])) {
  460. return $_SERVER[$var_name];
  461. }
  462. if (isset($_ENV[$var_name])) {
  463. return $_ENV[$var_name];
  464. }
  465. if (getenv($var_name)) {
  466. return getenv($var_name);
  467. }
  468. if (function_exists('apache_getenv')
  469. && apache_getenv($var_name, true)
  470. ) {
  471. return apache_getenv($var_name, true);
  472. }
  473. return '';
  474. }
  475. /**
  476. * Send HTTP header, taking IIS limits into account (600 seems ok)
  477. *
  478. * @param string $uri the header to send
  479. * @param bool $use_refresh whether to use Refresh: header when running on IIS
  480. *
  481. * @return void
  482. */
  483. public static function sendHeaderLocation($uri, $use_refresh = false)
  484. {
  485. if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && mb_strlen($uri) > 600) {
  486. Response::getInstance()->disable();
  487. echo Template::get('header_location')
  488. ->render(array('uri' => $uri));
  489. return;
  490. }
  491. /*
  492. * Avoid relative path redirect problems in case user entered URL
  493. * like /phpmyadmin/index.php/ which some web servers happily accept.
  494. */
  495. if ($uri[0] == '.') {
  496. $uri = $GLOBALS['PMA_Config']->getRootPath() . substr($uri, 2);
  497. }
  498. $response = Response::getInstance();
  499. session_write_close();
  500. if ($response->headersSent()) {
  501. trigger_error(
  502. 'Core::sendHeaderLocation called when headers are already sent!',
  503. E_USER_ERROR
  504. );
  505. }
  506. // bug #1523784: IE6 does not like 'Refresh: 0', it
  507. // results in a blank page
  508. // but we need it when coming from the cookie login panel)
  509. if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && $use_refresh) {
  510. $response->header('Refresh: 0; ' . $uri);
  511. } else {
  512. $response->header('Location: ' . $uri);
  513. }
  514. }
  515. /**
  516. * Outputs application/json headers. This includes no caching.
  517. *
  518. * @return void
  519. */
  520. public static function headerJSON()
  521. {
  522. if (defined('TESTSUITE')) {
  523. return;
  524. }
  525. // No caching
  526. self::noCacheHeader();
  527. // MIME type
  528. header('Content-Type: application/json; charset=UTF-8');
  529. // Disable content sniffing in browser
  530. // This is needed in case we include HTML in JSON, browser might assume it's
  531. // html to display
  532. header('X-Content-Type-Options: nosniff');
  533. }
  534. /**
  535. * Outputs headers to prevent caching in browser (and on the way).
  536. *
  537. * @return void
  538. */
  539. public static function noCacheHeader()
  540. {
  541. if (defined('TESTSUITE')) {
  542. return;
  543. }
  544. // rfc2616 - Section 14.21
  545. header('Expires: ' . gmdate(DATE_RFC1123));
  546. // HTTP/1.1
  547. header(
  548. 'Cache-Control: no-store, no-cache, must-revalidate,'
  549. . ' pre-check=0, post-check=0, max-age=0'
  550. );
  551. header('Pragma: no-cache'); // HTTP/1.0
  552. // test case: exporting a database into a .gz file with Safari
  553. // would produce files not having the current time
  554. // (added this header for Safari but should not harm other browsers)
  555. header('Last-Modified: ' . gmdate(DATE_RFC1123));
  556. }
  557. /**
  558. * Sends header indicating file download.
  559. *
  560. * @param string $filename Filename to include in headers if empty,
  561. * none Content-Disposition header will be sent.
  562. * @param string $mimetype MIME type to include in headers.
  563. * @param int $length Length of content (optional)
  564. * @param bool $no_cache Whether to include no-caching headers.
  565. *
  566. * @return void
  567. */
  568. public static function downloadHeader($filename, $mimetype, $length = 0, $no_cache = true)
  569. {
  570. if ($no_cache) {
  571. self::noCacheHeader();
  572. }
  573. /* Replace all possibly dangerous chars in filename */
  574. $filename = Sanitize::sanitizeFilename($filename);
  575. if (!empty($filename)) {
  576. header('Content-Description: File Transfer');
  577. header('Content-Disposition: attachment; filename="' . $filename . '"');
  578. }
  579. header('Content-Type: ' . $mimetype);
  580. // inform the server that compression has been done,
  581. // to avoid a double compression (for example with Apache + mod_deflate)
  582. $notChromeOrLessThan43 = PMA_USR_BROWSER_AGENT != 'CHROME' // see bug #4942
  583. || (PMA_USR_BROWSER_AGENT == 'CHROME' && PMA_USR_BROWSER_VER < 43);
  584. if (strpos($mimetype, 'gzip') !== false && $notChromeOrLessThan43) {
  585. header('Content-Encoding: gzip');
  586. }
  587. header('Content-Transfer-Encoding: binary');
  588. if ($length > 0) {
  589. header('Content-Length: ' . $length);
  590. }
  591. }
  592. /**
  593. * Returns value of an element in $array given by $path.
  594. * $path is a string describing position of an element in an associative array,
  595. * eg. Servers/1/host refers to $array[Servers][1][host]
  596. *
  597. * @param string $path path in the array
  598. * @param array $array the array
  599. * @param mixed $default default value
  600. *
  601. * @return mixed array element or $default
  602. */
  603. public static function arrayRead($path, array $array, $default = null)
  604. {
  605. $keys = explode('/', $path);
  606. $value =& $array;
  607. foreach ($keys as $key) {
  608. if (! isset($value[$key])) {
  609. return $default;
  610. }
  611. $value =& $value[$key];
  612. }
  613. return $value;
  614. }
  615. /**
  616. * Stores value in an array
  617. *
  618. * @param string $path path in the array
  619. * @param array &$array the array
  620. * @param mixed $value value to store
  621. *
  622. * @return void
  623. */
  624. public static function arrayWrite($path, array &$array, $value)
  625. {
  626. $keys = explode('/', $path);
  627. $last_key = array_pop($keys);
  628. $a =& $array;
  629. foreach ($keys as $key) {
  630. if (! isset($a[$key])) {
  631. $a[$key] = array();
  632. }
  633. $a =& $a[$key];
  634. }
  635. $a[$last_key] = $value;
  636. }
  637. /**
  638. * Removes value from an array
  639. *
  640. * @param string $path path in the array
  641. * @param array &$array the array
  642. *
  643. * @return void
  644. */
  645. public static function arrayRemove($path, array &$array)
  646. {
  647. $keys = explode('/', $path);
  648. $keys_last = array_pop($keys);
  649. $path = array();
  650. $depth = 0;
  651. $path[0] =& $array;
  652. $found = true;
  653. // go as deep as required or possible
  654. foreach ($keys as $key) {
  655. if (! isset($path[$depth][$key])) {
  656. $found = false;
  657. break;
  658. }
  659. $depth++;
  660. $path[$depth] =& $path[$depth - 1][$key];
  661. }
  662. // if element found, remove it
  663. if ($found) {
  664. unset($path[$depth][$keys_last]);
  665. $depth--;
  666. }
  667. // remove empty nested arrays
  668. for (; $depth >= 0; $depth--) {
  669. if (! isset($path[$depth+1]) || count($path[$depth+1]) == 0) {
  670. unset($path[$depth][$keys[$depth]]);
  671. } else {
  672. break;
  673. }
  674. }
  675. }
  676. /**
  677. * Returns link to (possibly) external site using defined redirector.
  678. *
  679. * @param string $url URL where to go.
  680. *
  681. * @return string URL for a link.
  682. */
  683. public static function linkURL($url)
  684. {
  685. if (!preg_match('#^https?://#', $url)) {
  686. return $url;
  687. }
  688. $params = array();
  689. $params['url'] = $url;
  690. $url = Url::getCommon($params);
  691. //strip off token and such sensitive information. Just keep url.
  692. $arr = parse_url($url);
  693. parse_str($arr["query"], $vars);
  694. $query = http_build_query(array("url" => $vars["url"]));
  695. if (!is_null($GLOBALS['PMA_Config']) && $GLOBALS['PMA_Config']->get('is_setup')) {
  696. $url = '../url.php?' . $query;
  697. } else {
  698. $url = './url.php?' . $query;
  699. }
  700. return $url;
  701. }
  702. /**
  703. * Checks whether domain of URL is whitelisted domain or not.
  704. * Use only for URLs of external sites.
  705. *
  706. * @param string $url URL of external site.
  707. *
  708. * @return boolean True: if domain of $url is allowed domain,
  709. * False: otherwise.
  710. */
  711. public static function isAllowedDomain($url)
  712. {
  713. $arr = parse_url($url);
  714. // We need host to be set
  715. if (! isset($arr['host']) || strlen($arr['host']) == 0) {
  716. return false;
  717. }
  718. // We do not want these to be present
  719. $blocked = array('user', 'pass', 'port');
  720. foreach ($blocked as $part) {
  721. if (isset($arr[$part]) && strlen($arr[$part]) != 0) {
  722. return false;
  723. }
  724. }
  725. $domain = $arr["host"];
  726. $domainWhiteList = array(
  727. /* Include current domain */
  728. $_SERVER['SERVER_NAME'],
  729. /* phpMyAdmin domains */
  730. 'wiki.phpmyadmin.net',
  731. 'www.phpmyadmin.net',
  732. 'phpmyadmin.net',
  733. 'demo.phpmyadmin.net',
  734. 'docs.phpmyadmin.net',
  735. /* mysql.com domains */
  736. 'dev.mysql.com','bugs.mysql.com',
  737. /* mariadb domains */
  738. 'mariadb.org', 'mariadb.com',
  739. /* php.net domains */
  740. 'php.net',
  741. 'secure.php.net',
  742. /* Github domains*/
  743. 'github.com','www.github.com',
  744. /* Percona domains */
  745. 'www.percona.com',
  746. /* Following are doubtful ones. */
  747. 'mysqldatabaseadministration.blogspot.com',
  748. );
  749. return in_array($domain, $domainWhiteList);
  750. }
  751. /**
  752. * Replace some html-unfriendly stuff
  753. *
  754. * @param string $buffer String to process
  755. *
  756. * @return string Escaped and cleaned up text suitable for html
  757. */
  758. public static function mimeDefaultFunction($buffer)
  759. {
  760. $buffer = htmlspecialchars($buffer);
  761. $buffer = str_replace(' ', ' &nbsp;', $buffer);
  762. $buffer = preg_replace("@((\015\012)|(\015)|(\012))@", '<br />' . "\n", $buffer);
  763. return $buffer;
  764. }
  765. /**
  766. * Displays SQL query before executing.
  767. *
  768. * @param array|string $query_data Array containing queries or query itself
  769. *
  770. * @return void
  771. */
  772. public static function previewSQL($query_data)
  773. {
  774. $retval = '<div class="preview_sql">';
  775. if (empty($query_data)) {
  776. $retval .= __('No change');
  777. } elseif (is_array($query_data)) {
  778. foreach ($query_data as $query) {
  779. $retval .= Util::formatSql($query);
  780. }
  781. } else {
  782. $retval .= Util::formatSql($query_data);
  783. }
  784. $retval .= '</div>';
  785. $response = Response::getInstance();
  786. $response->addJSON('sql_data', $retval);
  787. exit;
  788. }
  789. /**
  790. * recursively check if variable is empty
  791. *
  792. * @param mixed $value the variable
  793. *
  794. * @return bool true if empty
  795. */
  796. public static function emptyRecursive($value)
  797. {
  798. $empty = true;
  799. if (is_array($value)) {
  800. array_walk_recursive(
  801. $value,
  802. function ($item) use (&$empty) {
  803. $empty = $empty && empty($item);
  804. }
  805. );
  806. } else {
  807. $empty = empty($value);
  808. }
  809. return $empty;
  810. }
  811. /**
  812. * Creates some globals from $_POST variables matching a pattern
  813. *
  814. * @param array $post_patterns The patterns to search for
  815. *
  816. * @return void
  817. */
  818. public static function setPostAsGlobal(array $post_patterns)
  819. {
  820. foreach (array_keys($_POST) as $post_key) {
  821. foreach ($post_patterns as $one_post_pattern) {
  822. if (preg_match($one_post_pattern, $post_key)) {
  823. $GLOBALS[$post_key] = $_POST[$post_key];
  824. }
  825. }
  826. }
  827. }
  828. /**
  829. * Creates some globals from $_REQUEST
  830. *
  831. * @param string $param db|table
  832. *
  833. * @return void
  834. */
  835. public static function setGlobalDbOrTable($param)
  836. {
  837. $GLOBALS[$param] = '';
  838. if (self::isValid($_REQUEST[$param])) {
  839. // can we strip tags from this?
  840. // only \ and / is not allowed in db names for MySQL
  841. $GLOBALS[$param] = $_REQUEST[$param];
  842. $GLOBALS['url_params'][$param] = $GLOBALS[$param];
  843. }
  844. }
  845. /**
  846. * PATH_INFO could be compromised if set, so remove it from PHP_SELF
  847. * and provide a clean PHP_SELF here
  848. *
  849. * @return void
  850. */
  851. public static function cleanupPathInfo()
  852. {
  853. global $PMA_PHP_SELF;
  854. $PMA_PHP_SELF = self::getenv('PHP_SELF');
  855. if (empty($PMA_PHP_SELF)) {
  856. $PMA_PHP_SELF = urldecode(self::getenv('REQUEST_URI'));
  857. }
  858. $_PATH_INFO = self::getenv('PATH_INFO');
  859. if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) {
  860. $question_pos = mb_strpos($PMA_PHP_SELF, '?');
  861. if ($question_pos != false) {
  862. $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos);
  863. }
  864. $path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO);
  865. if ($path_info_pos !== false) {
  866. $path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO));
  867. if ($path_info_part == $_PATH_INFO) {
  868. $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos);
  869. }
  870. }
  871. }
  872. $path = [];
  873. foreach(explode('/', $PMA_PHP_SELF) as $part) {
  874. // ignore parts that have no value
  875. if (empty($part) || $part === '.') continue;
  876. if ($part !== '..') {
  877. // cool, we found a new part
  878. array_push($path, $part);
  879. } elseif (count($path) > 0) {
  880. // going back up? sure
  881. array_pop($path);
  882. }
  883. // Here we intentionall ignore case where we go too up
  884. // as there is nothing sane to do
  885. }
  886. $PMA_PHP_SELF = htmlspecialchars('/' . join('/', $path));
  887. }
  888. /**
  889. * Checks that required PHP extensions are there.
  890. * @return void
  891. */
  892. public static function checkExtensions()
  893. {
  894. /**
  895. * Warning about mbstring.
  896. */
  897. if (! function_exists('mb_detect_encoding')) {
  898. self::warnMissingExtension('mbstring');
  899. }
  900. /**
  901. * We really need this one!
  902. */
  903. if (! function_exists('preg_replace')) {
  904. self::warnMissingExtension('pcre', true);
  905. }
  906. /**
  907. * JSON is required in several places.
  908. */
  909. if (! function_exists('json_encode')) {
  910. self::warnMissingExtension('json', true);
  911. }
  912. /**
  913. * ctype is required for Twig.
  914. */
  915. if (! function_exists('ctype_alpha')) {
  916. self::warnMissingExtension('ctype', true);
  917. }
  918. /**
  919. * hash is required for cookie authentication.
  920. */
  921. if (! function_exists('hash_hmac')) {
  922. self::warnMissingExtension('hash', true);
  923. }
  924. }
  925. /**
  926. * Gets the "true" IP address of the current user
  927. *
  928. * @return string the ip of the user
  929. *
  930. * @access private
  931. */
  932. public static function getIp()
  933. {
  934. /* Get the address of user */
  935. if (empty($_SERVER['REMOTE_ADDR'])) {
  936. /* We do not know remote IP */
  937. return false;
  938. }
  939. $direct_ip = $_SERVER['REMOTE_ADDR'];
  940. /* Do we trust this IP as a proxy? If yes we will use it's header. */
  941. if (!isset($GLOBALS['cfg']['TrustedProxies'][$direct_ip])) {
  942. /* Return true IP */
  943. return $direct_ip;
  944. }
  945. /**
  946. * Parse header in form:
  947. * X-Forwarded-For: client, proxy1, proxy2
  948. */
  949. // Get header content
  950. $value = self::getenv($GLOBALS['cfg']['TrustedProxies'][$direct_ip]);
  951. // Grab first element what is client adddress
  952. $value = explode(',', $value)[0];
  953. // checks that the header contains only one IP address,
  954. $is_ip = filter_var($value, FILTER_VALIDATE_IP);
  955. if ($is_ip !== false) {
  956. // True IP behind a proxy
  957. return $value;
  958. }
  959. // We could not parse header
  960. return false;
  961. } // end of the 'getIp()' function
  962. /**
  963. * Sanitizes MySQL hostname
  964. *
  965. * * strips p: prefix(es)
  966. *
  967. * @param string $name User given hostname
  968. *
  969. * @return string
  970. */
  971. public static function sanitizeMySQLHost($name)
  972. {
  973. while (strtolower(substr($name, 0, 2)) == 'p:') {
  974. $name = substr($name, 2);
  975. }
  976. return $name;
  977. }
  978. /**
  979. * Sanitizes MySQL username
  980. *
  981. * * strips part behind null byte
  982. *
  983. * @param string $name User given username
  984. *
  985. * @return string
  986. */
  987. public static function sanitizeMySQLUser($name)
  988. {
  989. $position = strpos($name, chr(0));
  990. if ($position !== false) {
  991. return substr($name, 0, $position);
  992. }
  993. return $name;
  994. }
  995. /**
  996. * Safe unserializer wrapper
  997. *
  998. * It does not unserialize data containing objects
  999. *
  1000. * @param string $data Data to unserialize
  1001. *
  1002. * @return mixed
  1003. */
  1004. public static function safeUnserialize($data)
  1005. {
  1006. if (! is_string($data)) {
  1007. return null;
  1008. }
  1009. /* validate serialized data */
  1010. $length = strlen($data);
  1011. $depth = 0;
  1012. for ($i = 0; $i < $length; $i++) {
  1013. $value = $data[$i];
  1014. switch ($value)
  1015. {
  1016. case '}':
  1017. /* end of array */
  1018. if ($depth <= 0) {
  1019. return null;
  1020. }
  1021. $depth--;
  1022. break;
  1023. case 's':
  1024. /* string */
  1025. // parse sting length
  1026. $strlen = intval(substr($data, $i + 2));
  1027. // string start
  1028. $i = strpos($data, ':', $i + 2);
  1029. if ($i === false) {
  1030. return null;
  1031. }
  1032. // skip string, quotes and ;
  1033. $i += 2 + $strlen + 1;
  1034. if ($data[$i] != ';') {
  1035. return null;
  1036. }
  1037. break;
  1038. case 'b':
  1039. case 'i':
  1040. case 'd':
  1041. /* bool, integer or double */
  1042. // skip value to sepearator
  1043. $i = strpos($data, ';', $i);
  1044. if ($i === false) {
  1045. return null;
  1046. }
  1047. break;
  1048. case 'a':
  1049. /* array */
  1050. // find array start
  1051. $i = strpos($data, '{', $i);
  1052. if ($i === false) {
  1053. return null;
  1054. }
  1055. // remember nesting
  1056. $depth++;
  1057. break;
  1058. case 'N':
  1059. /* null */
  1060. // skip to end
  1061. $i = strpos($data, ';', $i);
  1062. if ($i === false) {
  1063. return null;
  1064. }
  1065. break;
  1066. default:
  1067. /* any other elements are not wanted */
  1068. return null;
  1069. }
  1070. }
  1071. // check unterminated arrays
  1072. if ($depth > 0) {
  1073. return null;
  1074. }
  1075. return unserialize($data);
  1076. }
  1077. /**
  1078. * Applies changes to PHP configuration.
  1079. *
  1080. * @return void
  1081. */
  1082. public static function configure()
  1083. {
  1084. /**
  1085. * Set utf-8 encoding for PHP
  1086. */
  1087. ini_set('default_charset', 'utf-8');
  1088. mb_internal_encoding('utf-8');
  1089. /**
  1090. * Set precision to sane value, with higher values
  1091. * things behave slightly unexpectedly, for example
  1092. * round(1.2, 2) returns 1.199999999999999956.
  1093. */
  1094. ini_set('precision', 14);
  1095. /**
  1096. * check timezone setting
  1097. * this could produce an E_WARNING - but only once,
  1098. * if not done here it will produce E_WARNING on every date/time function
  1099. */
  1100. date_default_timezone_set(@date_default_timezone_get());
  1101. }
  1102. /**
  1103. * Check whether PHP configuration matches our needs.
  1104. *
  1105. * @return void
  1106. */
  1107. public static function checkConfiguration()
  1108. {
  1109. /**
  1110. * As we try to handle charsets by ourself, mbstring overloads just
  1111. * break it, see bug 1063821.
  1112. *
  1113. * We specifically use empty here as we are looking for anything else than
  1114. * empty value or 0.
  1115. */
  1116. if (extension_loaded('mbstring') && !empty(ini_get('mbstring.func_overload'))) {
  1117. self::fatalError(
  1118. __(
  1119. 'You have enabled mbstring.func_overload in your PHP '
  1120. . 'configuration. This option is incompatible with phpMyAdmin '
  1121. . 'and might cause some data to be corrupted!'
  1122. )
  1123. );
  1124. }
  1125. /**
  1126. * The ini_set and ini_get functions can be disabled using
  1127. * disable_functions but we're relying quite a lot of them.
  1128. */
  1129. if (! function_exists('ini_get') || ! function_exists('ini_set')) {
  1130. self::fatalError(
  1131. __(
  1132. 'You have disabled ini_get and/or ini_set in php.ini. '
  1133. . 'This option is incompatible with phpMyAdmin!'
  1134. )
  1135. );
  1136. }
  1137. }
  1138. /**
  1139. * prints list item for main page
  1140. *
  1141. * @param string $name displayed text
  1142. * @param string $listId id, used for css styles
  1143. * @param string $url make item as link with $url as target
  1144. * @param string $mysql_help_page display a link to MySQL's manual
  1145. * @param string $target special target for $url
  1146. * @param string $a_id id for the anchor,
  1147. * used for jQuery to hook in functions
  1148. * @param string $class class for the li element
  1149. * @param string $a_class class for the anchor element
  1150. *
  1151. * @return void
  1152. */
  1153. public static function printListItem($name, $listId = null, $url = null,
  1154. $mysql_help_page = null, $target = null, $a_id = null, $class = null,
  1155. $a_class = null
  1156. ) {
  1157. echo Template::get('list/item')
  1158. ->render(
  1159. array(
  1160. 'content' => $name,
  1161. 'id' => $listId,
  1162. 'class' => $class,
  1163. 'url' => array(
  1164. 'href' => $url,
  1165. 'target' => $target,
  1166. 'id' => $a_id,
  1167. 'class' => $a_class,
  1168. ),
  1169. 'mysql_help_page' => $mysql_help_page,
  1170. )
  1171. );
  1172. }
  1173. /**
  1174. * Checks request and fails with fatal error if something problematic is found
  1175. *
  1176. * @return void
  1177. */
  1178. public static function checkRequest()
  1179. {
  1180. if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {
  1181. self::fatalError(__("GLOBALS overwrite attempt"));
  1182. }
  1183. /**
  1184. * protect against possible exploits - there is no need to have so much variables
  1185. */
  1186. if (count($_REQUEST) > 1000) {
  1187. self::fatalError(__('possible exploit'));
  1188. }
  1189. }
  1190. }