2 /* vim: set expandtab sw=4 ts=4 sts=4: */
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.
10 use PMA\libraries\Message
;
11 use PMA\libraries\Response
;
12 use PMA\libraries\URL
;
13 use PMA\libraries\Sanitize
;
16 if (! defined('PHPMYADMIN')) {
21 * String handling (security)
23 require_once 'libraries/string.lib.php';
26 * checks given $var and returns it if valid, or $default of not valid
27 * given $var is also checked for type being 'similar' as $default
28 * or against any other type if $type is provided
31 * // $_REQUEST['db'] not set
32 * echo PMA_ifSetOr($_REQUEST['db'], ''); // ''
33 * // $_REQUEST['sql_query'] not set
34 * echo PMA_ifSetOr($_REQUEST['sql_query']); // null
35 * // $cfg['EnableFoo'] not set
36 * echo PMA_ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false
37 * echo PMA_ifSetOr($cfg['EnableFoo']); // null
38 * // $cfg['EnableFoo'] set to 1
39 * echo PMA_ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false
40 * echo PMA_ifSetOr($cfg['EnableFoo'], false, 'similar'); // 1
41 * echo PMA_ifSetOr($cfg['EnableFoo'], false); // 1
42 * // $cfg['EnableFoo'] set to true
43 * echo PMA_ifSetOr($cfg['EnableFoo'], false, 'boolean'); // true
46 * @param mixed &$var param to check
47 * @param mixed $default default value
48 * @param mixed $type var type or array of values to check against $var
50 * @return mixed $var or $default
54 function PMA_ifSetOr(&$var, $default = null, $type = 'similar')
56 if (! PMA_isValid($var, $type, $default)) {
64 * checks given $var against $type or $compare
67 * - false : no type checking
68 * - 'scalar' : whether type of $var is integer, float, string or boolean
69 * - 'numeric' : whether type of $var is any number representation
70 * - 'length' : whether type of $var is scalar with a string length > 0
71 * - 'similar' : whether type of $var is similar to type of $compare
72 * - 'equal' : whether type of $var is identical to type of $compare
73 * - 'identical' : whether $var is identical to $compare, not only the type!
74 * - or any other valid PHP variable type
77 * // $_REQUEST['doit'] = true;
78 * PMA_isValid($_REQUEST['doit'], 'identical', 'true'); // false
79 * // $_REQUEST['doit'] = 'true';
80 * PMA_isValid($_REQUEST['doit'], 'identical', 'true'); // true
83 * NOTE: call-by-reference is used to not get NOTICE on undefined vars,
84 * but the var is not altered inside this function, also after checking a var
85 * this var exists nut is not set, example:
88 * isset($var); // false
89 * functionCallByReference($var); // false
90 * isset($var); // true
91 * functionCallByReference($var); // true
94 * to avoid this we set this var to null if not isset
96 * @param mixed &$var variable to check
97 * @param mixed $type var type or array of valid values to check against $var
98 * @param mixed $compare var to compare with $var
100 * @return boolean whether valid or not
102 * @todo add some more var types like hex, bin, ...?
103 * @see https://secure.php.net/gettype
105 function PMA_isValid(&$var, $type = 'length', $compare = null)
108 // var is not even set
112 if ($type === false) {
113 // no vartype requested
117 if (is_array($type)) {
118 return in_array($var, $type);
121 // allow some aliases of var types
122 $type = strtolower($type);
144 if ($type === 'identical') {
145 return $var === $compare;
148 // whether we should check against given $compare
149 if ($type === 'similar') {
150 switch (gettype($compare)) {
160 $type = gettype($compare);
162 } elseif ($type === 'equal') {
163 $type = gettype($compare);
167 if ($type === 'length' ||
$type === 'scalar') {
168 $is_scalar = is_scalar($var);
169 if ($is_scalar && $type === 'length') {
170 return strlen($var) > 0;
175 if ($type === 'numeric') {
176 return is_numeric($var);
179 if (gettype($var) === $type) {
187 * Removes insecure parts in a path; used before include() or
188 * require() when a part of the path comes from an insecure source
189 * like a cookie or form.
191 * @param string $path The path to check
193 * @return string The secured path
197 function PMA_securePath($path)
200 $path = preg_replace('@\.\.*@', '.', $path);
206 * displays the given error message on phpMyAdmin error page in foreign language,
207 * ends script execution and closes session
209 * loads language file if not loaded already
211 * @param string $error_message the error message or named error message
212 * @param string|array $message_args arguments applied to $error_message
216 function PMA_fatalError($error_message, $message_args = null) {
217 /* Use format string if applicable */
218 if (is_string($message_args)) {
219 $error_message = sprintf($error_message, $message_args);
220 } elseif (is_array($message_args)) {
221 $error_message = vsprintf($error_message, $message_args);
225 * Avoid using Response class as config does not have to be loaded yet
226 * (this can happen on early fatal error)
228 if (! empty($_REQUEST['ajax_request'])) {
229 // Generate JSON manually
234 'error' => Message
::error($error_message)->getDisplay(),
238 $error_message = strtr($error_message, array('<br />' => '[br]'));
240 // these variables are used in the included file libraries/error.inc.php
241 //first check if php-mbstring is available
242 if (function_exists('mb_detect_encoding')) {
243 //If present use gettext
244 $error_header = __('Error');
246 $error_header = 'Error';
248 $lang = isset($GLOBALS['lang']) ?
$GLOBALS['lang'] : 'en';
249 $dir = isset($GLOBALS['text_dir']) ?
$GLOBALS['text_dir'] : 'ltr';
251 // Displays the error message
252 include './libraries/error.inc.php';
254 if (! defined('TESTSUITE')) {
260 * Returns a link to the PHP documentation
262 * @param string $target anchor in documentation
264 * @return string the URL
268 function PMA_getPHPDocLink($target)
270 /* List of PHP documentation translations */
271 $php_doc_languages = array(
272 'pt_BR', 'zh', 'fr', 'de', 'it', 'ja', 'pl', 'ro', 'ru', 'fa', 'es', 'tr'
276 if (in_array($GLOBALS['lang'], $php_doc_languages)) {
277 $lang = $GLOBALS['lang'];
280 return PMA_linkURL('https://secure.php.net/manual/' . $lang . '/' . $target);
284 * Warn or fail on missing extension.
286 * @param string $extension Extension name
287 * @param bool $fatal Whether the error is fatal.
288 * @param string $extra Extra string to append to message.
292 function PMA_warnMissingExtension($extension, $fatal = false, $extra = '')
294 /* Gettext does not have to be loaded yet here */
295 if (function_exists('__')) {
297 'The %s extension is missing. Please check your PHP configuration.'
301 = 'The %s extension is missing. Please check your PHP configuration.';
303 $doclink = PMA_getPHPDocLink('book.' . $extension . '.php');
306 '[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]'
309 $message .= ' ' . $extra;
312 PMA_fatalError($message);
316 $GLOBALS['error_handler']->addError(
326 * returns count of tables in given db
328 * @param string $db database to count tables for
330 * @return integer count of tables in $db
332 function PMA_getTableCount($db)
334 $tables = $GLOBALS['dbi']->tryQuery(
335 'SHOW TABLES FROM ' . PMA\libraries\Util
::backquote($db) . ';',
336 null, PMA\libraries\DatabaseInterface
::QUERY_STORE
339 $num_tables = $GLOBALS['dbi']->numRows($tables);
340 $GLOBALS['dbi']->freeResult($tables);
349 * Converts numbers like 10M into bytes
350 * Used with permission from Moodle (https://moodle.org) by Martin Dougiamas
351 * (renamed with PMA prefix to avoid double definition when embedded
354 * @param string|int $size size (Default = 0)
356 * @return integer $size
358 function PMA_getRealSize($size = 0)
364 $binaryprefixes = array(
365 'T' => 1099511627776,
366 't' => 1099511627776,
375 if (preg_match('/^([0-9]+)([KMGT])/i', $size, $matches)) {
376 return $matches[1] * $binaryprefixes[$matches[2]];
380 } // end function PMA_getRealSize()
383 * boolean phpMyAdmin.PMA_checkPageValidity(string &$page, array $whitelist)
385 * checks given $page against given $whitelist and returns true if valid
386 * it optionally ignores query parameters in $page (script.php?ignored)
388 * @param string &$page page to check
389 * @param array $whitelist whitelist to check page against
391 * @return boolean whether $page is valid or not (in $whitelist or not)
393 function PMA_checkPageValidity(&$page, $whitelist)
395 if (! isset($page) ||
!is_string($page)) {
399 if (in_array($page, $whitelist)) {
406 mb_strpos($page . '?', '?')
408 if (in_array($_page, $whitelist)) {
412 $_page = urldecode($page);
416 mb_strpos($_page . '?', '?')
418 if (in_array($_page, $whitelist)) {
426 * tries to find the value for the given environment variable name
428 * searches in $_SERVER, $_ENV then tries getenv() and apache_getenv()
431 * @param string $var_name variable name
433 * @return string value of $var or empty string
435 function PMA_getenv($var_name)
437 if (isset($_SERVER[$var_name])) {
438 return $_SERVER[$var_name];
441 if (isset($_ENV[$var_name])) {
442 return $_ENV[$var_name];
445 if (getenv($var_name)) {
446 return getenv($var_name);
449 if (function_exists('apache_getenv')
450 && apache_getenv($var_name, true)
452 return apache_getenv($var_name, true);
459 * Send HTTP header, taking IIS limits into account (600 seems ok)
461 * @param string $uri the header to send
462 * @param bool $use_refresh whether to use Refresh: header when running on IIS
466 function PMA_sendHeaderLocation($uri, $use_refresh = false)
468 if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && mb_strlen($uri) > 600) {
469 Response
::getInstance()->disable();
471 echo PMA\libraries\Template
::get('header_location')
472 ->render(array('uri' => $uri));
478 * Avoid relative path redirect problems in case user entered URL
479 * like /phpmyadmin/index.php/ which some web servers happily accept.
481 if ($uri[0] == '.') {
482 $uri = $GLOBALS['PMA_Config']->getRootPath() . substr($uri, 2);
485 $response = Response
::getInstance();
487 session_write_close();
488 if ($response->headersSent()) {
490 'PMA_sendHeaderLocation called when headers are already sent!',
494 // bug #1523784: IE6 does not like 'Refresh: 0', it
495 // results in a blank page
496 // but we need it when coming from the cookie login panel)
497 if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && $use_refresh) {
498 $response->header('Refresh: 0; ' . $uri);
500 $response->header('Location: ' . $uri);
505 * Outputs application/json headers. This includes no caching.
509 function PMA_headerJSON()
511 if (defined('TESTSUITE')) {
517 header('Content-Type: application/json; charset=UTF-8');
518 // Disable content sniffing in browser
519 // This is needed in case we include HTML in JSON, browser might assume it's
521 header('X-Content-Type-Options: nosniff');
525 * Outputs headers to prevent caching in browser (and on the way).
529 function PMA_noCacheHeader()
531 if (defined('TESTSUITE')) {
534 // rfc2616 - Section 14.21
535 header('Expires: ' . gmdate(DATE_RFC1123
));
538 'Cache-Control: no-store, no-cache, must-revalidate,'
539 . ' pre-check=0, post-check=0, max-age=0'
542 header('Pragma: no-cache'); // HTTP/1.0
543 // test case: exporting a database into a .gz file with Safari
544 // would produce files not having the current time
545 // (added this header for Safari but should not harm other browsers)
546 header('Last-Modified: ' . gmdate(DATE_RFC1123
));
551 * Sends header indicating file download.
553 * @param string $filename Filename to include in headers if empty,
554 * none Content-Disposition header will be sent.
555 * @param string $mimetype MIME type to include in headers.
556 * @param int $length Length of content (optional)
557 * @param bool $no_cache Whether to include no-caching headers.
561 function PMA_downloadHeader($filename, $mimetype, $length = 0, $no_cache = true)
566 /* Replace all possibly dangerous chars in filename */
567 $filename = Sanitize
::sanitizeFilename($filename);
568 if (!empty($filename)) {
569 header('Content-Description: File Transfer');
570 header('Content-Disposition: attachment; filename="' . $filename . '"');
572 header('Content-Type: ' . $mimetype);
573 // inform the server that compression has been done,
574 // to avoid a double compression (for example with Apache + mod_deflate)
575 $notChromeOrLessThan43 = PMA_USR_BROWSER_AGENT
!= 'CHROME' // see bug #4942
576 ||
(PMA_USR_BROWSER_AGENT
== 'CHROME' && PMA_USR_BROWSER_VER
< 43);
577 if (strpos($mimetype, 'gzip') !== false && $notChromeOrLessThan43) {
578 header('Content-Encoding: gzip');
580 header('Content-Transfer-Encoding: binary');
582 header('Content-Length: ' . $length);
587 * Returns value of an element in $array given by $path.
588 * $path is a string describing position of an element in an associative array,
589 * eg. Servers/1/host refers to $array[Servers][1][host]
591 * @param string $path path in the array
592 * @param array $array the array
593 * @param mixed $default default value
595 * @return mixed array element or $default
597 function PMA_arrayRead($path, $array, $default = null)
599 $keys = explode('/', $path);
601 foreach ($keys as $key) {
602 if (! isset($value[$key])) {
605 $value =& $value[$key];
611 * Stores value in an array
613 * @param string $path path in the array
614 * @param array &$array the array
615 * @param mixed $value value to store
619 function PMA_arrayWrite($path, &$array, $value)
621 $keys = explode('/', $path);
622 $last_key = array_pop($keys);
624 foreach ($keys as $key) {
625 if (! isset($a[$key])) {
630 $a[$last_key] = $value;
634 * Removes value from an array
636 * @param string $path path in the array
637 * @param array &$array the array
641 function PMA_arrayRemove($path, &$array)
643 $keys = explode('/', $path);
644 $keys_last = array_pop($keys);
650 // go as deep as required or possible
651 foreach ($keys as $key) {
652 if (! isset($path[$depth][$key])) {
657 $path[$depth] =& $path[$depth - 1][$key];
659 // if element found, remove it
661 unset($path[$depth][$keys_last]);
665 // remove empty nested arrays
666 for (; $depth >= 0; $depth--) {
667 if (! isset($path[$depth+
1]) ||
count($path[$depth+
1]) == 0) {
668 unset($path[$depth][$keys[$depth]]);
676 * Returns link to (possibly) external site using defined redirector.
678 * @param string $url URL where to go.
680 * @return string URL for a link.
682 function PMA_linkURL($url)
684 if (!preg_match('#^https?://#', $url)) {
689 $params['url'] = $url;
691 $url = URL
::getCommon($params);
692 //strip off token and such sensitive information. Just keep url.
693 $arr = parse_url($url);
694 parse_str($arr["query"], $vars);
695 $query = http_build_query(array("url" => $vars["url"]));
697 if (defined('PMA_SETUP')) {
698 $url = '../url.php?' . $query;
700 $url = './url.php?' . $query;
707 * Checks whether domain of URL is whitelisted domain or not.
708 * Use only for URLs of external sites.
710 * @param string $url URL of external site.
712 * @return boolean True: if domain of $url is allowed domain,
715 function PMA_isAllowedDomain($url)
717 $arr = parse_url($url);
718 // We need host to be set
719 if (! isset($arr['host']) ||
strlen($arr['host']) == 0) {
722 // We do not want these to be present
723 $blocked = array('user', 'pass', 'port');
724 foreach ($blocked as $part) {
725 if (isset($arr[$part]) && strlen($arr[$part]) != 0) {
729 $domain = $arr["host"];
730 $domainWhiteList = array(
731 /* Include current domain */
732 $_SERVER['SERVER_NAME'],
733 /* phpMyAdmin domains */
734 'wiki.phpmyadmin.net',
735 'www.phpmyadmin.net',
737 'demo.phpmyadmin.net',
738 'docs.phpmyadmin.net',
739 /* mysql.com domains */
740 'dev.mysql.com','bugs.mysql.com',
741 /* mariadb domains */
742 'mariadb.org', 'mariadb.com',
743 /* php.net domains */
746 /* sourceforge.net domain */
749 'github.com','www.github.com',
750 /* Percona domains */
752 /* Following are doubtful ones. */
753 'mysqldatabaseadministration.blogspot.com',
755 if (in_array($domain, $domainWhiteList)) {
763 * Replace some html-unfriendly stuff
765 * @param string $buffer String to process
767 * @return string Escaped and cleaned up text suitable for html
769 function PMA_mimeDefaultFunction($buffer)
771 $buffer = htmlspecialchars($buffer);
772 $buffer = str_replace(' ', ' ', $buffer);
773 $buffer = preg_replace("@((\015\012)|(\015)|(\012))@", '<br />' . "\n", $buffer);
779 * Displays SQL query before executing.
781 * @param array|string $query_data Array containing queries or query itself
785 function PMA_previewSQL($query_data)
787 $retval = '<div class="preview_sql">';
788 if (empty($query_data)) {
789 $retval .= __('No change');
790 } elseif (is_array($query_data)) {
791 foreach ($query_data as $query) {
792 $retval .= PMA\libraries\Util
::formatSql($query);
795 $retval .= PMA\libraries\Util
::formatSql($query_data);
798 $response = Response
::getInstance();
799 $response->addJSON('sql_data', $retval);
804 * recursively check if variable is empty
806 * @param mixed $value the variable
808 * @return bool true if empty
810 function PMA_emptyRecursive($value)
813 if (is_array($value)) {
814 array_walk_recursive(
816 function ($item) use (&$empty) {
817 $empty = $empty && empty($item);
821 $empty = empty($value);
827 * Creates some globals from $_POST variables matching a pattern
829 * @param array $post_patterns The patterns to search for
833 function PMA_setPostAsGlobal($post_patterns)
835 foreach (array_keys($_POST) as $post_key) {
836 foreach ($post_patterns as $one_post_pattern) {
837 if (preg_match($one_post_pattern, $post_key)) {
838 $GLOBALS[$post_key] = $_POST[$post_key];
845 * Creates some globals from $_REQUEST
847 * @param string $param db|table
851 function PMA_setGlobalDbOrTable($param)
853 $GLOBALS[$param] = '';
854 if (PMA_isValid($_REQUEST[$param])) {
855 // can we strip tags from this?
856 // only \ and / is not allowed in db names for MySQL
857 $GLOBALS[$param] = $_REQUEST[$param];
858 $GLOBALS['url_params'][$param] = $GLOBALS[$param];
863 * PATH_INFO could be compromised if set, so remove it from PHP_SELF
864 * and provide a clean PHP_SELF here
868 function PMA_cleanupPathInfo()
870 global $PMA_PHP_SELF;
872 $PMA_PHP_SELF = PMA_getenv('PHP_SELF');
873 if (empty($PMA_PHP_SELF)) {
874 $PMA_PHP_SELF = urldecode(PMA_getenv('REQUEST_URI'));
876 $_PATH_INFO = PMA_getenv('PATH_INFO');
877 if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) {
878 $question_pos = mb_strpos($PMA_PHP_SELF, '?');
879 if ($question_pos != false) {
880 $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos);
882 $path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO);
883 if ($path_info_pos !== false) {
884 $path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO));
885 if ($path_info_part == $_PATH_INFO) {
886 $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos);
892 foreach(explode('/', $PMA_PHP_SELF) as $part) {
893 // ignore parts that have no value
894 if (empty($part) ||
$part === '.') continue;
896 if ($part !== '..') {
897 // cool, we found a new part
898 array_push($path, $part);
899 } else if (count($path) > 0) {
900 // going back up? sure
903 // Here we intentionall ignore case where we go too up
904 // as there is nothing sane to do
907 $PMA_PHP_SELF = htmlspecialchars('/' . join('/', $path));
911 * Checks that required PHP extensions are there.
914 function PMA_checkExtensions()
917 * Warning about mbstring.
919 if (! function_exists('mb_detect_encoding')) {
920 PMA_warnMissingExtension('mbstring', true);
924 * We really need this one!
926 if (! function_exists('preg_replace')) {
927 PMA_warnMissingExtension('pcre', true);
931 * JSON is required in several places.
933 if (! function_exists('json_encode')) {
934 PMA_warnMissingExtension('json', true);
939 * Gets the "true" IP address of the current user
941 * @return string the ip of the user
947 /* Get the address of user */
948 if (empty($_SERVER['REMOTE_ADDR'])) {
949 /* We do not know remote IP */
953 $direct_ip = $_SERVER['REMOTE_ADDR'];
955 /* Do we trust this IP as a proxy? If yes we will use it's header. */
956 if (!isset($GLOBALS['cfg']['TrustedProxies'][$direct_ip])) {
962 * Parse header in form:
963 * X-Forwarded-For: client, proxy1, proxy2
965 // Get header content
966 $value = PMA_getenv($GLOBALS['cfg']['TrustedProxies'][$direct_ip]);
967 // Grab first element what is client adddress
968 $value = explode(',', $value)[0];
969 // checks that the header contains only one IP address,
970 $is_ip = filter_var($value, FILTER_VALIDATE_IP
);
972 if ($is_ip !== false) {
973 // True IP behind a proxy
977 // We could not parse header
979 } // end of the 'PMA_getIp()' function
982 /* Compatibility with PHP < 5.6 */
983 if(! function_exists('hash_equals')) {
986 * Timing attack safe string comparison
988 * @param string $a first string
989 * @param string $b second string
991 * @return boolean whether they are equal
993 function hash_equals($a, $b) {
994 $ret = strlen($a) ^
strlen($b);
995 $ret |
= array_sum(unpack("C*", $a ^
$b));
999 /* Compatibility with PHP < 5.1 or PHP without hash extension */
1000 if (! function_exists('hash_hmac')) {
1001 function hash_hmac($algo, $data, $key, $raw_output = false)
1003 $algo = strtolower($algo);
1004 $pack = 'H'.strlen($algo('test'));
1006 $opad = str_repeat(chr(0x5C), $size);
1007 $ipad = str_repeat(chr(0x36), $size);
1009 if (strlen($key) > $size) {
1010 $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
1012 $key = str_pad($key, $size, chr(0x00));
1015 for ($i = 0; $i < strlen($key) - 1; $i++
) {
1016 $opad[$i] = $opad[$i] ^
$key[$i];
1017 $ipad[$i] = $ipad[$i] ^
$key[$i];
1020 $output = $algo($opad.pack($pack, $algo($ipad.$data)));
1022 return ($raw_output) ?
pack($pack, $output) : $output;
1027 * Sanitizes MySQL hostname
1029 * * strips p: prefix(es)
1031 * @param string $name User given hostname
1035 function PMA_sanitizeMySQLHost($name)
1037 while (strtolower(substr($name, 0, 2)) == 'p:') {
1038 $name = substr($name, 2);
1045 * Sanitizes MySQL username
1047 * * strips part behind null byte
1049 * @param string $name User given username
1053 function PMA_sanitizeMySQLUser($name)
1055 $position = strpos($name, chr(0));
1056 if ($position !== false) {
1057 return substr($name, 0, $position);
1063 * Safe unserializer wrapper
1065 * It does not unserialize data containing objects
1067 * @param string $data Data to unserialize
1071 function PMA_safeUnserialize($data)
1073 if (! is_string($data)) {
1077 /* validate serialized data */
1078 $length = strlen($data);
1080 for ($i = 0; $i < $length; $i++
) {
1094 // parse sting length
1095 $strlen = intval(substr($data, $i +
2));
1097 $i = strpos($data, ':', $i +
2);
1101 // skip string, quotes and ;
1102 $i +
= 2 +
$strlen +
1;
1103 if ($data[$i] != ';') {
1111 /* bool, integer or double */
1112 // skip value to sepearator
1113 $i = strpos($data, ';', $i);
1121 $i = strpos($data, '{', $i);
1131 $i = strpos($data, ';', $i);
1137 /* any other elements are not wanted */
1142 // check unterminated arrays
1147 return unserialize($data);