PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/classes/Config.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 1384 lines | 1062 code | 119 blank | 203 comment | 94 complexity | a157c9b4f3664712ae4076666f7cdcbc MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use PhpMyAdmin\Config\Settings;
  5. use function __;
  6. use function array_filter;
  7. use function array_merge;
  8. use function array_replace_recursive;
  9. use function array_slice;
  10. use function count;
  11. use function defined;
  12. use function error_get_last;
  13. use function error_reporting;
  14. use function explode;
  15. use function fclose;
  16. use function file_exists;
  17. use function filemtime;
  18. use function fileperms;
  19. use function fopen;
  20. use function fread;
  21. use function function_exists;
  22. use function gd_info;
  23. use function get_object_vars;
  24. use function implode;
  25. use function ini_get;
  26. use function intval;
  27. use function is_dir;
  28. use function is_int;
  29. use function is_numeric;
  30. use function is_readable;
  31. use function is_string;
  32. use function is_writable;
  33. use function mb_strstr;
  34. use function mb_strtolower;
  35. use function md5;
  36. use function min;
  37. use function mkdir;
  38. use function ob_end_clean;
  39. use function ob_get_clean;
  40. use function ob_start;
  41. use function parse_url;
  42. use function preg_match;
  43. use function realpath;
  44. use function rtrim;
  45. use function setcookie;
  46. use function sprintf;
  47. use function str_contains;
  48. use function str_replace;
  49. use function stripos;
  50. use function strlen;
  51. use function strtolower;
  52. use function substr;
  53. use function sys_get_temp_dir;
  54. use function time;
  55. use function trigger_error;
  56. use function trim;
  57. use const ARRAY_FILTER_USE_KEY;
  58. use const DIRECTORY_SEPARATOR;
  59. use const E_USER_ERROR;
  60. use const PHP_OS;
  61. use const PHP_URL_PATH;
  62. use const PHP_URL_SCHEME;
  63. use const PHP_VERSION_ID;
  64. /**
  65. * Configuration handling
  66. */
  67. class Config
  68. {
  69. /** @var array default configuration settings */
  70. public $default = [];
  71. /** @var array configuration settings, without user preferences applied */
  72. public $baseSettings = [];
  73. /** @var array configuration settings */
  74. public $settings = [];
  75. /** @var string config source */
  76. public $source = '';
  77. /** @var int source modification time */
  78. public $sourceMtime = 0;
  79. /** @var int */
  80. public $setMtime = 0;
  81. /** @var bool */
  82. public $errorConfigFile = false;
  83. /** @var array */
  84. public $defaultServer = [];
  85. /**
  86. * @var bool whether init is done or not
  87. * set this to false to force some initial checks
  88. * like checking for required functions
  89. */
  90. public $done = false;
  91. /**
  92. * @param string $source source to read config from
  93. */
  94. public function __construct(?string $source = null)
  95. {
  96. $this->settings = ['is_setup' => false];
  97. // functions need to refresh in case of config file changed goes in
  98. // PhpMyAdmin\Config::load()
  99. $this->load($source);
  100. // other settings, independent from config file, comes in
  101. $this->checkSystem();
  102. $this->baseSettings = $this->settings;
  103. }
  104. /**
  105. * sets system and application settings
  106. */
  107. public function checkSystem(): void
  108. {
  109. $this->checkWebServerOs();
  110. $this->checkWebServer();
  111. $this->checkGd2();
  112. $this->checkClient();
  113. $this->checkUpload();
  114. $this->checkUploadSize();
  115. $this->checkOutputCompression();
  116. }
  117. /**
  118. * whether to use gzip output compression or not
  119. */
  120. public function checkOutputCompression(): void
  121. {
  122. // If zlib output compression is set in the php configuration file, no
  123. // output buffering should be run
  124. if (ini_get('zlib.output_compression')) {
  125. $this->set('OBGzip', false);
  126. }
  127. // enable output-buffering (if set to 'auto')
  128. if (strtolower((string) $this->get('OBGzip')) !== 'auto') {
  129. return;
  130. }
  131. $this->set('OBGzip', true);
  132. }
  133. /**
  134. * Sets the client platform based on user agent
  135. *
  136. * @param string $user_agent the user agent
  137. */
  138. private function setClientPlatform(string $user_agent): void
  139. {
  140. if (mb_strstr($user_agent, 'Win')) {
  141. $this->set('PMA_USR_OS', 'Win');
  142. } elseif (mb_strstr($user_agent, 'Mac')) {
  143. $this->set('PMA_USR_OS', 'Mac');
  144. } elseif (mb_strstr($user_agent, 'Linux')) {
  145. $this->set('PMA_USR_OS', 'Linux');
  146. } elseif (mb_strstr($user_agent, 'Unix')) {
  147. $this->set('PMA_USR_OS', 'Unix');
  148. } elseif (mb_strstr($user_agent, 'OS/2')) {
  149. $this->set('PMA_USR_OS', 'OS/2');
  150. } else {
  151. $this->set('PMA_USR_OS', 'Other');
  152. }
  153. }
  154. /**
  155. * Determines platform (OS), browser and version of the user
  156. * Based on a phpBuilder article:
  157. *
  158. * @see http://www.phpbuilder.net/columns/tim20000821.php
  159. */
  160. public function checkClient(): void
  161. {
  162. $HTTP_USER_AGENT = '';
  163. if (Core::getenv('HTTP_USER_AGENT')) {
  164. $HTTP_USER_AGENT = Core::getenv('HTTP_USER_AGENT');
  165. }
  166. // 1. Platform
  167. $this->setClientPlatform($HTTP_USER_AGENT);
  168. // 2. browser and version
  169. // (must check everything else before Mozilla)
  170. $is_mozilla = preg_match('@Mozilla/([0-9]\.[0-9]{1,2})@', $HTTP_USER_AGENT, $mozilla_version);
  171. if (preg_match('@Opera(/| )([0-9]\.[0-9]{1,2})@', $HTTP_USER_AGENT, $log_version)) {
  172. $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
  173. $this->set('PMA_USR_BROWSER_AGENT', 'OPERA');
  174. } elseif (preg_match('@(MS)?IE ([0-9]{1,2}\.[0-9]{1,2})@', $HTTP_USER_AGENT, $log_version)) {
  175. $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
  176. $this->set('PMA_USR_BROWSER_AGENT', 'IE');
  177. } elseif (preg_match('@Trident/(7)\.0@', $HTTP_USER_AGENT, $log_version)) {
  178. $this->set('PMA_USR_BROWSER_VER', intval($log_version[1]) + 4);
  179. $this->set('PMA_USR_BROWSER_AGENT', 'IE');
  180. } elseif (preg_match('@OmniWeb/([0-9]{1,3})@', $HTTP_USER_AGENT, $log_version)) {
  181. $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
  182. $this->set('PMA_USR_BROWSER_AGENT', 'OMNIWEB');
  183. // Konqueror 2.2.2 says Konqueror/2.2.2
  184. // Konqueror 3.0.3 says Konqueror/3
  185. } elseif (preg_match('@(Konqueror/)(.*)(;)@', $HTTP_USER_AGENT, $log_version)) {
  186. $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
  187. $this->set('PMA_USR_BROWSER_AGENT', 'KONQUEROR');
  188. // must check Chrome before Safari
  189. } elseif ($is_mozilla && preg_match('@Chrome/([0-9.]*)@', $HTTP_USER_AGENT, $log_version)) {
  190. $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
  191. $this->set('PMA_USR_BROWSER_AGENT', 'CHROME');
  192. // newer Safari
  193. } elseif ($is_mozilla && preg_match('@Version/(.*) Safari@', $HTTP_USER_AGENT, $log_version)) {
  194. $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
  195. $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
  196. // older Safari
  197. } elseif ($is_mozilla && preg_match('@Safari/([0-9]*)@', $HTTP_USER_AGENT, $log_version)) {
  198. $this->set('PMA_USR_BROWSER_VER', $mozilla_version[1] . '.' . $log_version[1]);
  199. $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
  200. // Firefox
  201. } elseif (
  202. ! mb_strstr($HTTP_USER_AGENT, 'compatible')
  203. && preg_match('@Firefox/([\w.]+)@', $HTTP_USER_AGENT, $log_version)
  204. ) {
  205. $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
  206. $this->set('PMA_USR_BROWSER_AGENT', 'FIREFOX');
  207. } elseif (preg_match('@rv:1\.9(.*)Gecko@', $HTTP_USER_AGENT)) {
  208. $this->set('PMA_USR_BROWSER_VER', '1.9');
  209. $this->set('PMA_USR_BROWSER_AGENT', 'GECKO');
  210. } elseif ($is_mozilla) {
  211. $this->set('PMA_USR_BROWSER_VER', $mozilla_version[1]);
  212. $this->set('PMA_USR_BROWSER_AGENT', 'MOZILLA');
  213. } else {
  214. $this->set('PMA_USR_BROWSER_VER', 0);
  215. $this->set('PMA_USR_BROWSER_AGENT', 'OTHER');
  216. }
  217. }
  218. /**
  219. * Whether GD2 is present
  220. */
  221. public function checkGd2(): void
  222. {
  223. if ($this->get('GD2Available') === 'yes') {
  224. $this->set('PMA_IS_GD2', 1);
  225. return;
  226. }
  227. if ($this->get('GD2Available') === 'no') {
  228. $this->set('PMA_IS_GD2', 0);
  229. return;
  230. }
  231. if (! function_exists('imagecreatetruecolor')) {
  232. $this->set('PMA_IS_GD2', 0);
  233. return;
  234. }
  235. if (function_exists('gd_info')) {
  236. $gd_nfo = gd_info();
  237. if (mb_strstr($gd_nfo['GD Version'], '2.')) {
  238. $this->set('PMA_IS_GD2', 1);
  239. return;
  240. }
  241. }
  242. $this->set('PMA_IS_GD2', 0);
  243. }
  244. /**
  245. * Whether the Web server php is running on is IIS
  246. */
  247. public function checkWebServer(): void
  248. {
  249. // some versions return Microsoft-IIS, some Microsoft/IIS
  250. // we could use a preg_match() but it's slower
  251. if (
  252. Core::getenv('SERVER_SOFTWARE')
  253. && stripos(Core::getenv('SERVER_SOFTWARE'), 'Microsoft') !== false
  254. && stripos(Core::getenv('SERVER_SOFTWARE'), 'IIS') !== false
  255. ) {
  256. $this->set('PMA_IS_IIS', 1);
  257. return;
  258. }
  259. $this->set('PMA_IS_IIS', 0);
  260. }
  261. /**
  262. * Whether the os php is running on is windows or not
  263. */
  264. public function checkWebServerOs(): void
  265. {
  266. // Default to Unix or Equiv
  267. $this->set('PMA_IS_WINDOWS', false);
  268. // If PHP_OS is defined then continue
  269. if (! defined('PHP_OS')) {
  270. return;
  271. }
  272. if (stripos(PHP_OS, 'win') !== false && stripos(PHP_OS, 'darwin') === false) {
  273. // Is it some version of Windows
  274. $this->set('PMA_IS_WINDOWS', true);
  275. } elseif (stripos(PHP_OS, 'OS/2') !== false) {
  276. // Is it OS/2 (No file permissions like Windows)
  277. $this->set('PMA_IS_WINDOWS', true);
  278. }
  279. }
  280. /**
  281. * loads default values from default source
  282. */
  283. public function loadDefaults(): void
  284. {
  285. $settings = new Settings([]);
  286. $cfg = $settings->toArray();
  287. // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
  288. $this->defaultServer = get_object_vars($settings->Servers[1]);
  289. unset($cfg['Servers']);
  290. $this->default = $cfg;
  291. $this->settings = array_replace_recursive($this->settings, $cfg);
  292. }
  293. /**
  294. * loads configuration from $source, usually the config file
  295. * should be called on object creation
  296. *
  297. * @param string $source config file
  298. */
  299. public function load(?string $source = null): bool
  300. {
  301. global $isConfigLoading;
  302. $this->loadDefaults();
  303. if ($source !== null) {
  304. $this->setSource($source);
  305. }
  306. if (! $this->checkConfigSource()) {
  307. return false;
  308. }
  309. $cfg = [];
  310. /**
  311. * Parses the configuration file, we throw away any errors or
  312. * output.
  313. */
  314. $canUseErrorReporting = Util::isErrorReportingAvailable();
  315. $oldErrorReporting = null;
  316. if ($canUseErrorReporting) {
  317. $oldErrorReporting = error_reporting(0);
  318. }
  319. ob_start();
  320. $isConfigLoading = true;
  321. /** @psalm-suppress UnresolvableInclude */
  322. $eval_result = include $this->getSource();
  323. $isConfigLoading = false;
  324. ob_end_clean();
  325. if ($canUseErrorReporting) {
  326. error_reporting($oldErrorReporting);
  327. }
  328. if ($eval_result === false) {
  329. $this->errorConfigFile = true;
  330. } else {
  331. $this->errorConfigFile = false;
  332. $this->sourceMtime = (int) filemtime($this->getSource());
  333. }
  334. /**
  335. * Ignore keys with / as we do not use these
  336. *
  337. * These can be confusing for user configuration layer as it
  338. * flatten array using / and thus don't see difference between
  339. * $cfg['Export/method'] and $cfg['Export']['method'], while rest
  340. * of the code uses the setting only in latter form.
  341. *
  342. * This could be removed once we consistently handle both values
  343. * in the functional code as well.
  344. */
  345. $cfg = array_filter(
  346. $cfg,
  347. static function (string $key): bool {
  348. return ! str_contains($key, '/');
  349. },
  350. ARRAY_FILTER_USE_KEY
  351. );
  352. $this->settings = array_replace_recursive($this->settings, $cfg);
  353. return true;
  354. }
  355. /**
  356. * Sets the connection collation
  357. */
  358. private function setConnectionCollation(): void
  359. {
  360. global $dbi;
  361. $collation_connection = $this->get('DefaultConnectionCollation');
  362. if (empty($collation_connection) || $collation_connection == $GLOBALS['collation_connection']) {
  363. return;
  364. }
  365. $dbi->setCollation($collation_connection);
  366. }
  367. /**
  368. * Loads user preferences and merges them with current config
  369. * must be called after control connection has been established
  370. */
  371. public function loadUserPreferences(): void
  372. {
  373. global $isMinimumCommon;
  374. $userPreferences = new UserPreferences();
  375. // index.php should load these settings, so that phpmyadmin.css.php
  376. // will have everything available in session cache
  377. $server = $GLOBALS['server'] ?? (! empty($GLOBALS['cfg']['ServerDefault'])
  378. ? $GLOBALS['cfg']['ServerDefault']
  379. : 0);
  380. $cache_key = 'server_' . $server;
  381. if ($server > 0 && ! isset($isMinimumCommon)) {
  382. // cache user preferences, use database only when needed
  383. if (
  384. ! isset($_SESSION['cache'][$cache_key]['userprefs'])
  385. || $_SESSION['cache'][$cache_key]['config_mtime'] < $this->sourceMtime
  386. ) {
  387. $prefs = $userPreferences->load();
  388. $_SESSION['cache'][$cache_key]['userprefs'] = $userPreferences->apply($prefs['config_data']);
  389. $_SESSION['cache'][$cache_key]['userprefs_mtime'] = $prefs['mtime'];
  390. $_SESSION['cache'][$cache_key]['userprefs_type'] = $prefs['type'];
  391. $_SESSION['cache'][$cache_key]['config_mtime'] = $this->sourceMtime;
  392. }
  393. } elseif ($server == 0 || ! isset($_SESSION['cache'][$cache_key]['userprefs'])) {
  394. $this->set('user_preferences', false);
  395. return;
  396. }
  397. $config_data = $_SESSION['cache'][$cache_key]['userprefs'];
  398. // type is 'db' or 'session'
  399. $this->set('user_preferences', $_SESSION['cache'][$cache_key]['userprefs_type']);
  400. $this->set('user_preferences_mtime', $_SESSION['cache'][$cache_key]['userprefs_mtime']);
  401. // load config array
  402. $this->settings = array_replace_recursive($this->settings, $config_data);
  403. $GLOBALS['cfg'] = array_replace_recursive($GLOBALS['cfg'], $config_data);
  404. if (isset($isMinimumCommon)) {
  405. return;
  406. }
  407. // settings below start really working on next page load, but
  408. // changes are made only in index.php so everything is set when
  409. // in frames
  410. // save theme
  411. /** @var ThemeManager $tmanager */
  412. $tmanager = ThemeManager::getInstance();
  413. if ($tmanager->getThemeCookie() || isset($_REQUEST['set_theme'])) {
  414. if (
  415. (! isset($config_data['ThemeDefault'])
  416. && $tmanager->theme->getId() !== 'original')
  417. || isset($config_data['ThemeDefault'])
  418. && $config_data['ThemeDefault'] != $tmanager->theme->getId()
  419. ) {
  420. $this->setUserValue(
  421. null,
  422. 'ThemeDefault',
  423. $tmanager->theme->getId(),
  424. 'original'
  425. );
  426. }
  427. } else {
  428. // no cookie - read default from settings
  429. if (
  430. $tmanager->theme !== null
  431. && $this->settings['ThemeDefault'] != $tmanager->theme->getId()
  432. && $tmanager->checkTheme($this->settings['ThemeDefault'])
  433. ) {
  434. $tmanager->setActiveTheme($this->settings['ThemeDefault']);
  435. $tmanager->setThemeCookie();
  436. }
  437. }
  438. // save language
  439. if ($this->issetCookie('pma_lang') || isset($_POST['lang'])) {
  440. if (
  441. (! isset($config_data['lang'])
  442. && $GLOBALS['lang'] !== 'en')
  443. || isset($config_data['lang'])
  444. && $GLOBALS['lang'] != $config_data['lang']
  445. ) {
  446. $this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en');
  447. }
  448. } else {
  449. // read language from settings
  450. if (isset($config_data['lang'])) {
  451. $language = LanguageManager::getInstance()->getLanguage($config_data['lang']);
  452. if ($language !== false) {
  453. $language->activate();
  454. $this->setCookie('pma_lang', $language->getCode());
  455. }
  456. }
  457. }
  458. // set connection collation
  459. $this->setConnectionCollation();
  460. }
  461. /**
  462. * Sets config value which is stored in user preferences (if available)
  463. * or in a cookie.
  464. *
  465. * If user preferences are not yet initialized, option is applied to
  466. * global config and added to a update queue, which is processed
  467. * by {@link loadUserPreferences()}
  468. *
  469. * @param string|null $cookie_name can be null
  470. * @param string $cfg_path configuration path
  471. * @param string $new_cfg_value new value
  472. * @param string|null $default_value default value
  473. *
  474. * @return true|Message
  475. */
  476. public function setUserValue(
  477. ?string $cookie_name,
  478. string $cfg_path,
  479. $new_cfg_value,
  480. $default_value = null
  481. ) {
  482. $userPreferences = new UserPreferences();
  483. $result = true;
  484. // use permanent user preferences if possible
  485. $prefs_type = $this->get('user_preferences');
  486. if ($prefs_type) {
  487. if ($default_value === null) {
  488. $default_value = Core::arrayRead($cfg_path, $this->default);
  489. }
  490. $result = $userPreferences->persistOption($cfg_path, $new_cfg_value, $default_value);
  491. }
  492. if ($prefs_type !== 'db' && $cookie_name) {
  493. // fall back to cookies
  494. if ($default_value === null) {
  495. $default_value = Core::arrayRead($cfg_path, $this->settings);
  496. }
  497. $this->setCookie($cookie_name, $new_cfg_value, $default_value);
  498. }
  499. Core::arrayWrite($cfg_path, $GLOBALS['cfg'], $new_cfg_value);
  500. Core::arrayWrite($cfg_path, $this->settings, $new_cfg_value);
  501. return $result;
  502. }
  503. /**
  504. * Reads value stored by {@link setUserValue()}
  505. *
  506. * @param string $cookie_name cookie name
  507. * @param mixed $cfg_value config value
  508. *
  509. * @return mixed
  510. */
  511. public function getUserValue(string $cookie_name, $cfg_value)
  512. {
  513. $cookie_exists = ! empty($this->getCookie($cookie_name));
  514. $prefs_type = $this->get('user_preferences');
  515. if ($prefs_type === 'db') {
  516. // permanent user preferences value exists, remove cookie
  517. if ($cookie_exists) {
  518. $this->removeCookie($cookie_name);
  519. }
  520. } elseif ($cookie_exists) {
  521. return $this->getCookie($cookie_name);
  522. }
  523. // return value from $cfg array
  524. return $cfg_value;
  525. }
  526. /**
  527. * set source
  528. *
  529. * @param string $source source
  530. */
  531. public function setSource(string $source): void
  532. {
  533. $this->source = trim($source);
  534. }
  535. /**
  536. * check config source
  537. */
  538. public function checkConfigSource(): bool
  539. {
  540. if (! $this->getSource()) {
  541. // no configuration file set at all
  542. return false;
  543. }
  544. if (! @file_exists($this->getSource())) {
  545. $this->sourceMtime = 0;
  546. return false;
  547. }
  548. if (! @is_readable($this->getSource())) {
  549. // manually check if file is readable
  550. // might be bug #3059806 Supporting running from CIFS/Samba shares
  551. $contents = false;
  552. $handle = @fopen($this->getSource(), 'r');
  553. if ($handle !== false) {
  554. $contents = @fread($handle, 1); // reading 1 byte is enough to test
  555. fclose($handle);
  556. }
  557. if ($contents === false) {
  558. $this->sourceMtime = 0;
  559. Core::fatalError(
  560. sprintf(
  561. function_exists('__')
  562. ? __('Existing configuration file (%s) is not readable.')
  563. : 'Existing configuration file (%s) is not readable.',
  564. $this->getSource()
  565. )
  566. );
  567. return false;
  568. }
  569. }
  570. return true;
  571. }
  572. /**
  573. * verifies the permissions on config file (if asked by configuration)
  574. * (must be called after config.inc.php has been merged)
  575. */
  576. public function checkPermissions(): void
  577. {
  578. // Check for permissions (on platforms that support it):
  579. if (! $this->get('CheckConfigurationPermissions') || ! @file_exists($this->getSource())) {
  580. return;
  581. }
  582. $perms = @fileperms($this->getSource());
  583. if ($perms === false || (! ($perms & 2))) {
  584. return;
  585. }
  586. // This check is normally done after loading configuration
  587. $this->checkWebServerOs();
  588. if ($this->get('PMA_IS_WINDOWS') === true) {
  589. return;
  590. }
  591. $this->sourceMtime = 0;
  592. Core::fatalError(
  593. __(
  594. 'Wrong permissions on configuration file, should not be world writable!'
  595. )
  596. );
  597. }
  598. /**
  599. * Checks for errors
  600. * (must be called after config.inc.php has been merged)
  601. */
  602. public function checkErrors(): void
  603. {
  604. if (! $this->errorConfigFile) {
  605. return;
  606. }
  607. $error = '[strong]' . __('Failed to read configuration file!') . '[/strong]'
  608. . '[br][br]'
  609. . __('This usually means there is a syntax error in it, please check any errors shown below.')
  610. . '[br][br]'
  611. . '[conferr]';
  612. trigger_error($error, E_USER_ERROR);
  613. }
  614. /**
  615. * returns specific config setting
  616. *
  617. * @param string $setting config setting
  618. *
  619. * @return mixed|null value
  620. */
  621. public function get(string $setting)
  622. {
  623. if (isset($this->settings[$setting])) {
  624. return $this->settings[$setting];
  625. }
  626. return null;
  627. }
  628. /**
  629. * sets configuration variable
  630. *
  631. * @param string $setting configuration option
  632. * @param mixed $value new value for configuration option
  633. */
  634. public function set(string $setting, $value): void
  635. {
  636. if (isset($this->settings[$setting]) && $this->settings[$setting] === $value) {
  637. return;
  638. }
  639. $this->settings[$setting] = $value;
  640. $this->setMtime = time();
  641. }
  642. /**
  643. * returns source for current config
  644. *
  645. * @return string config source
  646. */
  647. public function getSource(): string
  648. {
  649. return $this->source;
  650. }
  651. /**
  652. * checks if upload is enabled
  653. */
  654. public function checkUpload(): void
  655. {
  656. if (! ini_get('file_uploads')) {
  657. $this->set('enable_upload', false);
  658. return;
  659. }
  660. $this->set('enable_upload', true);
  661. // if set "php_admin_value file_uploads Off" in httpd.conf
  662. // ini_get() also returns the string "Off" in this case:
  663. if (strtolower((string) ini_get('file_uploads')) !== 'off') {
  664. return;
  665. }
  666. $this->set('enable_upload', false);
  667. }
  668. /**
  669. * Maximum upload size as limited by PHP
  670. * Used with permission from Moodle (https://moodle.org/) by Martin Dougiamas
  671. *
  672. * this section generates max_upload_size in bytes
  673. */
  674. public function checkUploadSize(): void
  675. {
  676. $fileSize = ini_get('upload_max_filesize');
  677. if (! $fileSize) {
  678. $fileSize = '5M';
  679. }
  680. $size = Core::getRealSize($fileSize);
  681. $postSize = ini_get('post_max_size');
  682. if ($postSize) {
  683. $size = min($size, Core::getRealSize($postSize));
  684. }
  685. $this->set('max_upload_size', $size);
  686. }
  687. /**
  688. * Checks if protocol is https
  689. *
  690. * This function checks if the https protocol on the active connection.
  691. */
  692. public function isHttps(): bool
  693. {
  694. if ($this->get('is_https') !== null) {
  695. return (bool) $this->get('is_https');
  696. }
  697. $url = $this->get('PmaAbsoluteUri');
  698. $is_https = false;
  699. if (! empty($url) && parse_url($url, PHP_URL_SCHEME) === 'https') {
  700. $is_https = true;
  701. } elseif (strtolower(Core::getenv('HTTP_SCHEME')) === 'https') {
  702. $is_https = true;
  703. } elseif (strtolower(Core::getenv('HTTPS')) === 'on') {
  704. $is_https = true;
  705. } elseif (strtolower(substr(Core::getenv('REQUEST_URI'), 0, 6)) === 'https:') {
  706. $is_https = true;
  707. } elseif (strtolower(Core::getenv('HTTP_HTTPS_FROM_LB')) === 'on') {
  708. // A10 Networks load balancer
  709. $is_https = true;
  710. } elseif (strtolower(Core::getenv('HTTP_FRONT_END_HTTPS')) === 'on') {
  711. $is_https = true;
  712. } elseif (strtolower(Core::getenv('HTTP_X_FORWARDED_PROTO')) === 'https') {
  713. $is_https = true;
  714. } elseif (strtolower(Core::getenv('HTTP_CLOUDFRONT_FORWARDED_PROTO')) === 'https') {
  715. // Amazon CloudFront, issue #15621
  716. $is_https = true;
  717. } elseif (Util::getProtoFromForwardedHeader(Core::getenv('HTTP_FORWARDED')) === 'https') {
  718. // RFC 7239 Forwarded header
  719. $is_https = true;
  720. } elseif (Core::getenv('SERVER_PORT') == 443) {
  721. $is_https = true;
  722. }
  723. $this->set('is_https', $is_https);
  724. return $is_https;
  725. }
  726. /**
  727. * Get phpMyAdmin root path
  728. *
  729. * @staticvar string|null $cookie_path
  730. */
  731. public function getRootPath(): string
  732. {
  733. static $cookie_path = null;
  734. if ($cookie_path !== null && ! defined('TESTSUITE')) {
  735. return $cookie_path;
  736. }
  737. $url = $this->get('PmaAbsoluteUri');
  738. if (! empty($url)) {
  739. $path = parse_url($url, PHP_URL_PATH);
  740. if (! empty($path)) {
  741. if (substr($path, -1) !== '/') {
  742. return $path . '/';
  743. }
  744. return $path;
  745. }
  746. }
  747. $parsedUrlPath = parse_url($GLOBALS['PMA_PHP_SELF'], PHP_URL_PATH);
  748. $parts = explode(
  749. '/',
  750. rtrim(str_replace('\\', '/', $parsedUrlPath), '/')
  751. );
  752. /* Remove filename */
  753. if (substr($parts[count($parts) - 1], -4) === '.php') {
  754. $parts = array_slice($parts, 0, count($parts) - 1);
  755. }
  756. /* Remove extra path from javascript calls */
  757. if (defined('PMA_PATH_TO_BASEDIR')) {
  758. $parts = array_slice($parts, 0, count($parts) - 1);
  759. }
  760. $parts[] = '';
  761. return implode('/', $parts);
  762. }
  763. /**
  764. * removes cookie
  765. *
  766. * @param string $cookieName name of cookie to remove
  767. */
  768. public function removeCookie(string $cookieName): bool
  769. {
  770. $httpCookieName = $this->getCookieName($cookieName);
  771. if ($this->issetCookie($cookieName)) {
  772. unset($_COOKIE[$httpCookieName]);
  773. }
  774. if (defined('TESTSUITE')) {
  775. return true;
  776. }
  777. return setcookie(
  778. $httpCookieName,
  779. '',
  780. time() - 3600,
  781. $this->getRootPath(),
  782. '',
  783. $this->isHttps()
  784. );
  785. }
  786. /**
  787. * sets cookie if value is different from current cookie value,
  788. * or removes if value is equal to default
  789. *
  790. * @param string $cookie name of cookie to remove
  791. * @param string $value new cookie value
  792. * @param string $default default value
  793. * @param int $validity validity of cookie in seconds (default is one month)
  794. * @param bool $httponly whether cookie is only for HTTP (and not for scripts)
  795. */
  796. public function setCookie(
  797. string $cookie,
  798. string $value,
  799. ?string $default = null,
  800. ?int $validity = null,
  801. bool $httponly = true
  802. ): bool {
  803. global $cfg;
  804. if (strlen($value) > 0 && $default !== null && $value === $default) {
  805. // default value is used
  806. if ($this->issetCookie($cookie)) {
  807. // remove cookie
  808. return $this->removeCookie($cookie);
  809. }
  810. return false;
  811. }
  812. if (strlen($value) === 0 && $this->issetCookie($cookie)) {
  813. // remove cookie, value is empty
  814. return $this->removeCookie($cookie);
  815. }
  816. $httpCookieName = $this->getCookieName($cookie);
  817. if (! $this->issetCookie($cookie) || $this->getCookie($cookie) !== $value) {
  818. // set cookie with new value
  819. /* Calculate cookie validity */
  820. if ($validity === null) {
  821. /* Valid for one month */
  822. $validity = time() + 2592000;
  823. } elseif ($validity == 0) {
  824. /* Valid for session */
  825. $validity = 0;
  826. } else {
  827. $validity = time() + $validity;
  828. }
  829. if (defined('TESTSUITE')) {
  830. $_COOKIE[$httpCookieName] = $value;
  831. return true;
  832. }
  833. if (PHP_VERSION_ID < 70300) {
  834. return setcookie(
  835. $httpCookieName,
  836. $value,
  837. $validity,
  838. $this->getRootPath() . '; samesite=' . $cfg['CookieSameSite'],
  839. '',
  840. $this->isHttps(),
  841. $httponly
  842. );
  843. }
  844. $optionalParams = [
  845. 'expires' => $validity,
  846. 'path' => $this->getRootPath(),
  847. 'domain' => '',
  848. 'secure' => $this->isHttps(),
  849. 'httponly' => $httponly,
  850. 'samesite' => $cfg['CookieSameSite'],
  851. ];
  852. return setcookie($httpCookieName, $value, $optionalParams);
  853. }
  854. // cookie has already $value as value
  855. return true;
  856. }
  857. /**
  858. * get cookie
  859. *
  860. * @param string $cookieName The name of the cookie to get
  861. *
  862. * @return mixed|null result of getCookie()
  863. */
  864. public function getCookie(string $cookieName)
  865. {
  866. if (isset($_COOKIE[$this->getCookieName($cookieName)])) {
  867. return $_COOKIE[$this->getCookieName($cookieName)];
  868. }
  869. return null;
  870. }
  871. /**
  872. * Get the real cookie name
  873. *
  874. * @param string $cookieName The name of the cookie
  875. */
  876. public function getCookieName(string $cookieName): string
  877. {
  878. return $cookieName . ( $this->isHttps() ? '_https' : '' );
  879. }
  880. /**
  881. * isset cookie
  882. *
  883. * @param string $cookieName The name of the cookie to check
  884. */
  885. public function issetCookie(string $cookieName): bool
  886. {
  887. return isset($_COOKIE[$this->getCookieName($cookieName)]);
  888. }
  889. /**
  890. * Error handler to catch fatal errors when loading configuration
  891. * file
  892. */
  893. public static function fatalErrorHandler(): void
  894. {
  895. global $isConfigLoading;
  896. if (! isset($isConfigLoading) || ! $isConfigLoading) {
  897. return;
  898. }
  899. $error = error_get_last();
  900. if ($error === null) {
  901. return;
  902. }
  903. Core::fatalError(
  904. sprintf(
  905. 'Failed to load phpMyAdmin configuration (%s:%s): %s',
  906. Error::relPath($error['file']),
  907. $error['line'],
  908. $error['message']
  909. )
  910. );
  911. }
  912. /**
  913. * Wrapper for footer/header rendering
  914. *
  915. * @param string $filename File to check and render
  916. * @param string $id Div ID
  917. */
  918. private static function renderCustom(string $filename, string $id): string
  919. {
  920. $retval = '';
  921. if (@file_exists($filename)) {
  922. $retval .= '<div id="' . $id . '" class="d-print-none">';
  923. ob_start();
  924. include $filename;
  925. $retval .= ob_get_clean();
  926. $retval .= '</div>';
  927. }
  928. return $retval;
  929. }
  930. /**
  931. * Renders user configured footer
  932. */
  933. public static function renderFooter(): string
  934. {
  935. return self::renderCustom(CUSTOM_FOOTER_FILE, 'pma_footer');
  936. }
  937. /**
  938. * Renders user configured footer
  939. */
  940. public static function renderHeader(): string
  941. {
  942. return self::renderCustom(CUSTOM_HEADER_FILE, 'pma_header');
  943. }
  944. /**
  945. * Returns temporary dir path
  946. *
  947. * @param string $name Directory name
  948. *
  949. * @staticvar array<string,string|null> $temp_dir
  950. */
  951. public function getTempDir(string $name): ?string
  952. {
  953. static $temp_dir = [];
  954. if (isset($temp_dir[$name]) && ! defined('TESTSUITE')) {
  955. return $temp_dir[$name];
  956. }
  957. $path = $this->get('TempDir');
  958. if (empty($path)) {
  959. $path = null;
  960. } else {
  961. $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
  962. if (! @is_dir($path)) {
  963. @mkdir($path, 0770, true);
  964. }
  965. if (! @is_dir($path) || ! @is_writable($path)) {
  966. $path = null;
  967. }
  968. }
  969. $temp_dir[$name] = $path;
  970. return $path;
  971. }
  972. /**
  973. * Returns temporary directory
  974. */
  975. public function getUploadTempDir(): ?string
  976. {
  977. // First try configured temp dir
  978. // Fallback to PHP upload_tmp_dir
  979. $dirs = [
  980. $this->getTempDir('upload'),
  981. ini_get('upload_tmp_dir'),
  982. sys_get_temp_dir(),
  983. ];
  984. foreach ($dirs as $dir) {
  985. if (! empty($dir) && @is_writable($dir)) {
  986. return realpath($dir);
  987. }
  988. }
  989. return null;
  990. }
  991. /**
  992. * Selects server based on request parameters.
  993. */
  994. public function selectServer(): int
  995. {
  996. $request = empty($_REQUEST['server']) ? 0 : $_REQUEST['server'];
  997. /**
  998. * Lookup server by name
  999. * (see FAQ 4.8)
  1000. */
  1001. if (! is_numeric($request)) {
  1002. foreach ($this->settings['Servers'] as $i => $server) {
  1003. $verboseToLower = mb_strtolower($server['verbose']);
  1004. $serverToLower = mb_strtolower($request);
  1005. if (
  1006. $server['host'] == $request
  1007. || $server['verbose'] == $request
  1008. || $verboseToLower == $serverToLower
  1009. || md5($verboseToLower) === $serverToLower
  1010. ) {
  1011. $request = $i;
  1012. break;
  1013. }
  1014. }
  1015. if (is_string($request)) {
  1016. $request = 0;
  1017. }
  1018. }
  1019. /**
  1020. * If no server is selected, make sure that $this->settings['Server'] is empty (so
  1021. * that nothing will work), and skip server authentication.
  1022. * We do NOT exit here, but continue on without logging into any server.
  1023. * This way, the welcome page will still come up (with no server info) and
  1024. * present a choice of servers in the case that there are multiple servers
  1025. * and '$this->settings['ServerDefault'] = 0' is set.
  1026. */
  1027. if (is_numeric($request) && ! empty($request) && ! empty($this->settings['Servers'][$request])) {
  1028. $server = $request;
  1029. $this->settings['Server'] = $this->settings['Servers'][$server];
  1030. } else {
  1031. if (! empty($this->settings['Servers'][$this->settings['ServerDefault']])) {
  1032. $server = $this->settings['ServerDefault'];
  1033. $this->settings['Server'] = $this->settings['Servers'][$server];
  1034. } else {
  1035. $server = 0;
  1036. $this->settings['Server'] = [];
  1037. }
  1038. }
  1039. return (int) $server;
  1040. }
  1041. /**
  1042. * Checks whether Servers configuration is valid and possibly apply fixups.
  1043. */
  1044. public function checkServers(): void
  1045. {
  1046. // Do we have some server?
  1047. if (! isset($this->settings['Servers']) || count($this->settings['Servers']) === 0) {
  1048. // No server => create one with defaults
  1049. $this->settings['Servers'] = [1 => $this->defaultServer];
  1050. return;
  1051. }
  1052. // We have server(s) => apply default configuration
  1053. $new_servers = [];
  1054. foreach ($this->settings['Servers'] as $server_index => $each_server) {
  1055. // Detect wrong configuration
  1056. if (! is_int($server_index) || $server_index < 1) {
  1057. trigger_error(
  1058. sprintf(__('Invalid server index: %s'), $server_index),
  1059. E_USER_ERROR
  1060. );
  1061. }
  1062. $each_server = array_merge($this->defaultServer, $each_server);
  1063. // Final solution to bug #582890
  1064. // If we are using a socket connection
  1065. // and there is nothing in the verbose server name
  1066. // or the host field, then generate a name for the server
  1067. // in the form of "Server 2", localized of course!
  1068. if (empty($each_server['host']) && empty($each_server['verbose'])) {
  1069. $each_server['verbose'] = sprintf(__('Server %d'), $server_index);
  1070. }
  1071. $new_servers[$server_index] = $each_server;
  1072. }
  1073. $this->settings['Servers'] = $new_servers;
  1074. }
  1075. /**
  1076. * Return connection parameters for the database server
  1077. *
  1078. * @param int $mode Connection mode on of CONNECT_USER, CONNECT_CONTROL
  1079. * or CONNECT_AUXILIARY.
  1080. * @param array|null $server Server information like host/port/socket/persistent
  1081. *
  1082. * @return array user, host and server settings array
  1083. */
  1084. public static function getConnectionParams(int $mode, ?array $server = null): array
  1085. {
  1086. global $cfg;
  1087. $user = null;
  1088. $password = null;
  1089. if ($mode == DatabaseInterface::CONNECT_USER) {
  1090. $user = $cfg['Server']['user'];
  1091. $password = $cfg['Server']['password'];
  1092. $server = $cfg['Server'];
  1093. } elseif ($mode == DatabaseInterface::CONNECT_CONTROL) {
  1094. $user = $cfg['Server']['controluser'];
  1095. $password = $cfg['Server']['controlpass'];
  1096. $server = [];
  1097. if (! empty($cfg['Server']['controlhost'])) {
  1098. $server['host'] = $cfg['Server']['controlhost'];
  1099. } else {
  1100. $server['host'] = $cfg['Server']['host'];
  1101. }
  1102. // Share the settings if the host is same
  1103. if ($server['host'] == $cfg['Server']['host']) {
  1104. $shared = [
  1105. 'port',
  1106. 'socket',
  1107. 'compress',
  1108. 'ssl',
  1109. 'ssl_key',
  1110. 'ssl_cert',
  1111. 'ssl_ca',
  1112. 'ssl_ca_path',
  1113. 'ssl_ciphers',
  1114. 'ssl_verify',
  1115. ];
  1116. foreach ($shared as $item) {
  1117. if (! isset($cfg['Server'][$item])) {
  1118. continue;
  1119. }
  1120. $server[$item] = $cfg['Server'][$item];
  1121. }
  1122. }
  1123. // Set configured port
  1124. if (! empty($cfg['Server']['controlport'])) {
  1125. $server['port'] = $cfg['Server']['controlport'];
  1126. }
  1127. // Set any configuration with control_ prefix
  1128. foreach ($cfg['Server'] as $key => $val) {
  1129. if (substr($key, 0, 8) !== 'control_') {
  1130. continue;
  1131. }
  1132. $server[substr($key, 8)] = $val;
  1133. }
  1134. } else {
  1135. if ($server === null) {
  1136. return [
  1137. null,
  1138. null,
  1139. null,
  1140. ];
  1141. }
  1142. if (isset($server['user'])) {
  1143. $user = $server['user'];
  1144. }
  1145. if (isset($server['password'])) {
  1146. $password = $server['password'];
  1147. }
  1148. }
  1149. // Perform sanity checks on some variables
  1150. $server['port'] = empty($server['port']) ? 0 : (int) $server['port'];
  1151. if (empty($server['socket'])) {
  1152. $server['socket'] = null;
  1153. }
  1154. if (empty($server['host'])) {
  1155. $server['host'] = 'localhost';
  1156. }
  1157. if (! isset($server['ssl'])) {
  1158. $server['ssl'] = false;
  1159. }
  1160. if (! isset($server['compress'])) {
  1161. $server['compress'] = false;
  1162. }
  1163. return [
  1164. $user,
  1165. $password,
  1166. $server,
  1167. ];
  1168. }
  1169. /**
  1170. * Get LoginCookieValidity from preferences cache.
  1171. *
  1172. * No generic solution for loading preferences from cache as some settings
  1173. * need to be kept for processing in loadUserPreferences().
  1174. *
  1175. * @see loadUserPreferences()
  1176. */
  1177. public function getLoginCookieValidityFromCache(int $server): void
  1178. {
  1179. global $cfg;
  1180. $cacheKey = 'server_' . $server;
  1181. if (! isset($_SESSION['cache'][$cacheKey]['userprefs']['LoginCookieValidity'])) {
  1182. return;
  1183. }
  1184. $value = $_SESSION['cache'][$cacheKey]['userprefs']['LoginCookieValidity'];
  1185. $this->set('LoginCookieValidity', $value);
  1186. $cfg['LoginCookieValidity'] = $value;
  1187. }
  1188. }