Config.php 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Configuration handling.
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin;
  10. use DirectoryIterator;
  11. use PhpMyAdmin\Config;
  12. use PhpMyAdmin\Core;
  13. use PhpMyAdmin\Error;
  14. use PhpMyAdmin\LanguageManager;
  15. use PhpMyAdmin\Message;
  16. use PhpMyAdmin\ThemeManager;
  17. use PhpMyAdmin\Url;
  18. use PhpMyAdmin\UserPreferences;
  19. use PhpMyAdmin\Util;
  20. use PhpMyAdmin\Utils\HttpRequest;
  21. /**
  22. * Indication for error handler (see end of this file).
  23. */
  24. $GLOBALS['pma_config_loading'] = false;
  25. /**
  26. * Configuration class
  27. *
  28. * @package PhpMyAdmin
  29. */
  30. class Config
  31. {
  32. /**
  33. * @var string default config source
  34. */
  35. public $default_source = ROOT_PATH . 'libraries/config.default.php';
  36. /**
  37. * @var array default configuration settings
  38. */
  39. public $default = [];
  40. /**
  41. * @var array configuration settings, without user preferences applied
  42. */
  43. public $base_settings = [];
  44. /**
  45. * @var array configuration settings
  46. */
  47. public $settings = [];
  48. /**
  49. * @var string config source
  50. */
  51. public $source = '';
  52. /**
  53. * @var int source modification time
  54. */
  55. public $source_mtime = 0;
  56. public $default_source_mtime = 0;
  57. public $set_mtime = 0;
  58. /**
  59. * @var boolean
  60. */
  61. public $error_config_file = false;
  62. /**
  63. * @var boolean
  64. */
  65. public $error_config_default_file = false;
  66. /**
  67. * @var array
  68. */
  69. public $default_server = [];
  70. /**
  71. * @var boolean whether init is done or not
  72. * set this to false to force some initial checks
  73. * like checking for required functions
  74. */
  75. public $done = false;
  76. /**
  77. * constructor
  78. *
  79. * @param string $source source to read config from
  80. */
  81. public function __construct(?string $source = null)
  82. {
  83. $this->settings = ['is_setup' => false];
  84. // functions need to refresh in case of config file changed goes in
  85. // PhpMyAdmin\Config::load()
  86. $this->load($source);
  87. // other settings, independent from config file, comes in
  88. $this->checkSystem();
  89. $this->base_settings = $this->settings;
  90. }
  91. /**
  92. * sets system and application settings
  93. *
  94. * @return void
  95. */
  96. public function checkSystem(): void
  97. {
  98. $this->set('PMA_VERSION', '5.0.2');
  99. /* Major version */
  100. $this->set(
  101. 'PMA_MAJOR_VERSION',
  102. implode('.', array_slice(explode('.', $this->get('PMA_VERSION'), 3), 0, 2))
  103. );
  104. $this->checkWebServerOs();
  105. $this->checkWebServer();
  106. $this->checkGd2();
  107. $this->checkClient();
  108. $this->checkUpload();
  109. $this->checkUploadSize();
  110. $this->checkOutputCompression();
  111. }
  112. /**
  113. * whether to use gzip output compression or not
  114. *
  115. * @return void
  116. */
  117. public function checkOutputCompression(): void
  118. {
  119. // If zlib output compression is set in the php configuration file, no
  120. // output buffering should be run
  121. if (ini_get('zlib.output_compression')) {
  122. $this->set('OBGzip', false);
  123. }
  124. // enable output-buffering (if set to 'auto')
  125. if (strtolower((string) $this->get('OBGzip')) == 'auto') {
  126. $this->set('OBGzip', true);
  127. }
  128. }
  129. /**
  130. * Sets the client platform based on user agent
  131. *
  132. * @param string $user_agent the user agent
  133. *
  134. * @return void
  135. */
  136. private function _setClientPlatform(string $user_agent): void
  137. {
  138. if (mb_strstr($user_agent, 'Win')) {
  139. $this->set('PMA_USR_OS', 'Win');
  140. } elseif (mb_strstr($user_agent, 'Mac')) {
  141. $this->set('PMA_USR_OS', 'Mac');
  142. } elseif (mb_strstr($user_agent, 'Linux')) {
  143. $this->set('PMA_USR_OS', 'Linux');
  144. } elseif (mb_strstr($user_agent, 'Unix')) {
  145. $this->set('PMA_USR_OS', 'Unix');
  146. } elseif (mb_strstr($user_agent, 'OS/2')) {
  147. $this->set('PMA_USR_OS', 'OS/2');
  148. } else {
  149. $this->set('PMA_USR_OS', 'Other');
  150. }
  151. }
  152. /**
  153. * Determines platform (OS), browser and version of the user
  154. * Based on a phpBuilder article:
  155. *
  156. * @see http://www.phpbuilder.net/columns/tim20000821.php
  157. *
  158. * @return void
  159. */
  160. public function checkClient(): void
  161. {
  162. if (Core::getenv('HTTP_USER_AGENT')) {
  163. $HTTP_USER_AGENT = Core::getenv('HTTP_USER_AGENT');
  164. } else {
  165. $HTTP_USER_AGENT = '';
  166. }
  167. // 1. Platform
  168. $this->_setClientPlatform($HTTP_USER_AGENT);
  169. // 2. browser and version
  170. // (must check everything else before Mozilla)
  171. $is_mozilla = preg_match(
  172. '@Mozilla/([0-9]\.[0-9]{1,2})@',
  173. $HTTP_USER_AGENT,
  174. $mozilla_version
  175. );
  176. if (preg_match(
  177. '@Opera(/| )([0-9]\.[0-9]{1,2})@',
  178. $HTTP_USER_AGENT,
  179. $log_version
  180. )) {
  181. $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
  182. $this->set('PMA_USR_BROWSER_AGENT', 'OPERA');
  183. } elseif (preg_match(
  184. '@(MS)?IE ([0-9]{1,2}\.[0-9]{1,2})@',
  185. $HTTP_USER_AGENT,
  186. $log_version
  187. )) {
  188. $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
  189. $this->set('PMA_USR_BROWSER_AGENT', 'IE');
  190. } elseif (preg_match(
  191. '@Trident/(7)\.0@',
  192. $HTTP_USER_AGENT,
  193. $log_version
  194. )) {
  195. $this->set('PMA_USR_BROWSER_VER', intval($log_version[1]) + 4);
  196. $this->set('PMA_USR_BROWSER_AGENT', 'IE');
  197. } elseif (preg_match(
  198. '@OmniWeb/([0-9]{1,3})@',
  199. $HTTP_USER_AGENT,
  200. $log_version
  201. )) {
  202. $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
  203. $this->set('PMA_USR_BROWSER_AGENT', 'OMNIWEB');
  204. // Konqueror 2.2.2 says Konqueror/2.2.2
  205. // Konqueror 3.0.3 says Konqueror/3
  206. } elseif (preg_match(
  207. '@(Konqueror/)(.*)(;)@',
  208. $HTTP_USER_AGENT,
  209. $log_version
  210. )) {
  211. $this->set('PMA_USR_BROWSER_VER', $log_version[2]);
  212. $this->set('PMA_USR_BROWSER_AGENT', 'KONQUEROR');
  213. // must check Chrome before Safari
  214. } elseif ($is_mozilla
  215. && preg_match('@Chrome/([0-9.]*)@', $HTTP_USER_AGENT, $log_version)
  216. ) {
  217. $this->set('PMA_USR_BROWSER_VER', $log_version[1]);
  218. $this->set('PMA_USR_BROWSER_AGENT', 'CHROME');
  219. // newer Safari
  220. } elseif ($is_mozilla
  221. && preg_match('@Version/(.*) Safari@', $HTTP_USER_AGENT, $log_version)
  222. ) {
  223. $this->set(
  224. 'PMA_USR_BROWSER_VER',
  225. $log_version[1]
  226. );
  227. $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
  228. // older Safari
  229. } elseif ($is_mozilla
  230. && preg_match('@Safari/([0-9]*)@', $HTTP_USER_AGENT, $log_version)
  231. ) {
  232. $this->set(
  233. 'PMA_USR_BROWSER_VER',
  234. $mozilla_version[1] . '.' . $log_version[1]
  235. );
  236. $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
  237. // Firefox
  238. } elseif (! mb_strstr($HTTP_USER_AGENT, 'compatible')
  239. && preg_match('@Firefox/([\w.]+)@', $HTTP_USER_AGENT, $log_version)
  240. ) {
  241. $this->set(
  242. 'PMA_USR_BROWSER_VER',
  243. $log_version[1]
  244. );
  245. $this->set('PMA_USR_BROWSER_AGENT', 'FIREFOX');
  246. } elseif (preg_match('@rv:1\.9(.*)Gecko@', $HTTP_USER_AGENT)) {
  247. $this->set('PMA_USR_BROWSER_VER', '1.9');
  248. $this->set('PMA_USR_BROWSER_AGENT', 'GECKO');
  249. } elseif ($is_mozilla) {
  250. $this->set('PMA_USR_BROWSER_VER', $mozilla_version[1]);
  251. $this->set('PMA_USR_BROWSER_AGENT', 'MOZILLA');
  252. } else {
  253. $this->set('PMA_USR_BROWSER_VER', 0);
  254. $this->set('PMA_USR_BROWSER_AGENT', 'OTHER');
  255. }
  256. }
  257. /**
  258. * Whether GD2 is present
  259. *
  260. * @return void
  261. */
  262. public function checkGd2(): void
  263. {
  264. if ($this->get('GD2Available') == 'yes') {
  265. $this->set('PMA_IS_GD2', 1);
  266. return;
  267. }
  268. if ($this->get('GD2Available') == 'no') {
  269. $this->set('PMA_IS_GD2', 0);
  270. return;
  271. }
  272. if (! function_exists('imagecreatetruecolor')) {
  273. $this->set('PMA_IS_GD2', 0);
  274. return;
  275. }
  276. if (function_exists('gd_info')) {
  277. $gd_nfo = gd_info();
  278. if (mb_strstr($gd_nfo["GD Version"], '2.')) {
  279. $this->set('PMA_IS_GD2', 1);
  280. } else {
  281. $this->set('PMA_IS_GD2', 0);
  282. }
  283. } else {
  284. $this->set('PMA_IS_GD2', 0);
  285. }
  286. }
  287. /**
  288. * Whether the Web server php is running on is IIS
  289. *
  290. * @return void
  291. */
  292. public function checkWebServer(): void
  293. {
  294. // some versions return Microsoft-IIS, some Microsoft/IIS
  295. // we could use a preg_match() but it's slower
  296. if (Core::getenv('SERVER_SOFTWARE')
  297. && false !== stripos(Core::getenv('SERVER_SOFTWARE'), 'Microsoft')
  298. && false !== stripos(Core::getenv('SERVER_SOFTWARE'), 'IIS')
  299. ) {
  300. $this->set('PMA_IS_IIS', 1);
  301. } else {
  302. $this->set('PMA_IS_IIS', 0);
  303. }
  304. }
  305. /**
  306. * Whether the os php is running on is windows or not
  307. *
  308. * @return void
  309. */
  310. public function checkWebServerOs(): void
  311. {
  312. // Default to Unix or Equiv
  313. $this->set('PMA_IS_WINDOWS', 0);
  314. // If PHP_OS is defined then continue
  315. if (defined('PHP_OS')) {
  316. if (false !== stripos(PHP_OS, 'win') && false === stripos(PHP_OS, 'darwin')) {
  317. // Is it some version of Windows
  318. $this->set('PMA_IS_WINDOWS', 1);
  319. } elseif (false !== stripos(PHP_OS, 'OS/2')) {
  320. // Is it OS/2 (No file permissions like Windows)
  321. $this->set('PMA_IS_WINDOWS', 1);
  322. }
  323. }
  324. }
  325. /**
  326. * detects if Git revision
  327. * @param string $git_location (optional) verified git directory
  328. * @return boolean
  329. */
  330. public function isGitRevision(&$git_location = null): bool
  331. {
  332. // PMA config check
  333. if (! $this->get('ShowGitRevision')) {
  334. return false;
  335. }
  336. // caching
  337. if (isset($_SESSION['is_git_revision'])
  338. && array_key_exists('git_location', $_SESSION)
  339. ) {
  340. // Define location using cached value
  341. $git_location = $_SESSION['git_location'];
  342. return $_SESSION['is_git_revision'];
  343. }
  344. // find out if there is a .git folder
  345. // or a .git file (--separate-git-dir)
  346. $git = '.git';
  347. if (is_dir($git)) {
  348. if (@is_file($git . '/config')) {
  349. $git_location = $git;
  350. } else {
  351. $_SESSION['git_location'] = null;
  352. $_SESSION['is_git_revision'] = false;
  353. return false;
  354. }
  355. } elseif (is_file($git)) {
  356. $contents = file_get_contents($git);
  357. $gitmatch = [];
  358. // Matches expected format
  359. if (! preg_match(
  360. '/^gitdir: (.*)$/',
  361. $contents,
  362. $gitmatch
  363. )) {
  364. $_SESSION['git_location'] = null;
  365. $_SESSION['is_git_revision'] = false;
  366. return false;
  367. } elseif (@is_dir($gitmatch[1])) {
  368. //Detected git external folder location
  369. $git_location = $gitmatch[1];
  370. } else {
  371. $_SESSION['git_location'] = null;
  372. $_SESSION['is_git_revision'] = false;
  373. return false;
  374. }
  375. } else {
  376. $_SESSION['git_location'] = null;
  377. $_SESSION['is_git_revision'] = false;
  378. return false;
  379. }
  380. // Define session for caching
  381. $_SESSION['git_location'] = $git_location;
  382. $_SESSION['is_git_revision'] = true;
  383. return true;
  384. }
  385. /**
  386. * detects Git revision, if running inside repo
  387. *
  388. * @return void
  389. */
  390. public function checkGitRevision(): void
  391. {
  392. // find out if there is a .git folder
  393. $git_folder = '';
  394. if (! $this->isGitRevision($git_folder)) {
  395. $this->set('PMA_VERSION_GIT', 0);
  396. return;
  397. }
  398. if (! $ref_head = @file_get_contents($git_folder . '/HEAD')) {
  399. $this->set('PMA_VERSION_GIT', 0);
  400. return;
  401. }
  402. if ($common_dir_contents = @file_get_contents($git_folder . '/commondir')) {
  403. $git_folder = $git_folder . DIRECTORY_SEPARATOR . trim($common_dir_contents);
  404. }
  405. $branch = false;
  406. // are we on any branch?
  407. if (false !== strpos($ref_head, '/')) {
  408. // remove ref: prefix
  409. $ref_head = substr(trim($ref_head), 5);
  410. if (substr($ref_head, 0, 11) === 'refs/heads/') {
  411. $branch = substr($ref_head, 11);
  412. } else {
  413. $branch = basename($ref_head);
  414. }
  415. $ref_file = $git_folder . '/' . $ref_head;
  416. if (@file_exists($ref_file)) {
  417. $hash = @file_get_contents($ref_file);
  418. if (! $hash) {
  419. $this->set('PMA_VERSION_GIT', 0);
  420. return;
  421. }
  422. $hash = trim($hash);
  423. } else {
  424. // deal with packed refs
  425. $packed_refs = @file_get_contents($git_folder . '/packed-refs');
  426. if (! $packed_refs) {
  427. $this->set('PMA_VERSION_GIT', 0);
  428. return;
  429. }
  430. // split file to lines
  431. $ref_lines = explode(PHP_EOL, $packed_refs);
  432. foreach ($ref_lines as $line) {
  433. // skip comments
  434. if ($line[0] == '#') {
  435. continue;
  436. }
  437. // parse line
  438. $parts = explode(' ', $line);
  439. // care only about named refs
  440. if (count($parts) != 2) {
  441. continue;
  442. }
  443. // have found our ref?
  444. if ($parts[1] == $ref_head) {
  445. $hash = $parts[0];
  446. break;
  447. }
  448. }
  449. if (! isset($hash)) {
  450. $this->set('PMA_VERSION_GIT', 0);
  451. // Could not find ref
  452. return;
  453. }
  454. }
  455. } else {
  456. $hash = trim($ref_head);
  457. }
  458. $commit = false;
  459. if (! preg_match('/^[0-9a-f]{40}$/i', $hash)) {
  460. $commit = false;
  461. } elseif (isset($_SESSION['PMA_VERSION_COMMITDATA_' . $hash])) {
  462. $commit = $_SESSION['PMA_VERSION_COMMITDATA_' . $hash];
  463. } elseif (function_exists('gzuncompress')) {
  464. $git_file_name = $git_folder . '/objects/'
  465. . substr($hash, 0, 2) . '/' . substr($hash, 2);
  466. if (@file_exists($git_file_name)) {
  467. if (! $commit = @file_get_contents($git_file_name)) {
  468. $this->set('PMA_VERSION_GIT', 0);
  469. return;
  470. }
  471. $commit = explode("\0", gzuncompress($commit), 2);
  472. $commit = explode("\n", $commit[1]);
  473. $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit;
  474. } else {
  475. $pack_names = [];
  476. // work with packed data
  477. $packs_file = $git_folder . '/objects/info/packs';
  478. if (@file_exists($packs_file)
  479. && $packs = @file_get_contents($packs_file)
  480. ) {
  481. // File exists. Read it, parse the file to get the names of the
  482. // packs. (to look for them in .git/object/pack directory later)
  483. foreach (explode("\n", $packs) as $line) {
  484. // skip blank lines
  485. if (strlen(trim($line)) == 0) {
  486. continue;
  487. }
  488. // skip non pack lines
  489. if ($line[0] != 'P') {
  490. continue;
  491. }
  492. // parse names
  493. $pack_names[] = substr($line, 2);
  494. }
  495. } else {
  496. // '.git/objects/info/packs' file can be missing
  497. // (atlease in mysGit)
  498. // File missing. May be we can look in the .git/object/pack
  499. // directory for all the .pack files and use that list of
  500. // files instead
  501. $dirIterator = new DirectoryIterator(
  502. $git_folder . '/objects/pack'
  503. );
  504. foreach ($dirIterator as $file_info) {
  505. $file_name = $file_info->getFilename();
  506. // if this is a .pack file
  507. if ($file_info->isFile() && substr($file_name, -5) == '.pack'
  508. ) {
  509. $pack_names[] = $file_name;
  510. }
  511. }
  512. }
  513. $hash = strtolower($hash);
  514. foreach ($pack_names as $pack_name) {
  515. $index_name = str_replace('.pack', '.idx', $pack_name);
  516. // load index
  517. $index_data = @file_get_contents(
  518. $git_folder . '/objects/pack/' . $index_name
  519. );
  520. if (! $index_data) {
  521. continue;
  522. }
  523. // check format
  524. if (substr($index_data, 0, 4) != "\377tOc") {
  525. continue;
  526. }
  527. // check version
  528. $version = unpack('N', substr($index_data, 4, 4));
  529. if ($version[1] != 2) {
  530. continue;
  531. }
  532. // parse fanout table
  533. $fanout = unpack(
  534. "N*",
  535. substr($index_data, 8, 256 * 4)
  536. );
  537. // find where we should search
  538. $firstbyte = intval(substr($hash, 0, 2), 16);
  539. // array is indexed from 1 and we need to get
  540. // previous entry for start
  541. if ($firstbyte == 0) {
  542. $start = 0;
  543. } else {
  544. $start = $fanout[$firstbyte];
  545. }
  546. $end = $fanout[$firstbyte + 1];
  547. // stupid linear search for our sha
  548. $found = false;
  549. $offset = 8 + (256 * 4);
  550. for ($position = $start; $position < $end; $position++) {
  551. $sha = strtolower(
  552. bin2hex(
  553. substr($index_data, $offset + ($position * 20), 20)
  554. )
  555. );
  556. if ($sha == $hash) {
  557. $found = true;
  558. break;
  559. }
  560. }
  561. if (! $found) {
  562. continue;
  563. }
  564. // read pack offset
  565. $offset = 8 + (256 * 4) + (24 * $fanout[256]);
  566. $pack_offset = unpack(
  567. 'N',
  568. substr($index_data, $offset + ($position * 4), 4)
  569. );
  570. $pack_offset = $pack_offset[1];
  571. // open pack file
  572. $pack_file = fopen(
  573. $git_folder . '/objects/pack/' . $pack_name,
  574. 'rb'
  575. );
  576. if ($pack_file === false) {
  577. continue;
  578. }
  579. // seek to start
  580. fseek($pack_file, $pack_offset);
  581. // parse header
  582. $header = ord(fread($pack_file, 1));
  583. $type = ($header >> 4) & 7;
  584. $hasnext = ($header & 128) >> 7;
  585. $size = $header & 0xf;
  586. $offset = 4;
  587. while ($hasnext) {
  588. $byte = ord(fread($pack_file, 1));
  589. $size |= ($byte & 0x7f) << $offset;
  590. $hasnext = ($byte & 128) >> 7;
  591. $offset += 7;
  592. }
  593. // we care only about commit objects
  594. if ($type != 1) {
  595. continue;
  596. }
  597. // read data
  598. $commit = fread($pack_file, $size);
  599. $commit = gzuncompress($commit);
  600. $commit = explode("\n", $commit);
  601. $_SESSION['PMA_VERSION_COMMITDATA_' . $hash] = $commit;
  602. fclose($pack_file);
  603. }
  604. }
  605. }
  606. $httpRequest = new HttpRequest();
  607. // check if commit exists in Github
  608. if ($commit !== false
  609. && isset($_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash])
  610. ) {
  611. $is_remote_commit = $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash];
  612. } else {
  613. $link = 'https://www.phpmyadmin.net/api/commit/' . $hash . '/';
  614. $is_found = $httpRequest->create($link, 'GET');
  615. switch ($is_found) {
  616. case false:
  617. $is_remote_commit = false;
  618. $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = false;
  619. break;
  620. case null:
  621. // no remote link for now, but don't cache this as Github is down
  622. $is_remote_commit = false;
  623. break;
  624. default:
  625. $is_remote_commit = true;
  626. $_SESSION['PMA_VERSION_REMOTECOMMIT_' . $hash] = true;
  627. if ($commit === false) {
  628. // if no local commit data, try loading from Github
  629. $commit_json = json_decode($is_found);
  630. }
  631. break;
  632. }
  633. }
  634. $is_remote_branch = false;
  635. if ($is_remote_commit && $branch !== false) {
  636. // check if branch exists in Github
  637. if (isset($_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash])) {
  638. $is_remote_branch = $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash];
  639. } else {
  640. $link = 'https://www.phpmyadmin.net/api/tree/' . $branch . '/';
  641. $is_found = $httpRequest->create($link, 'GET', true);
  642. switch ($is_found) {
  643. case true:
  644. $is_remote_branch = true;
  645. $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = true;
  646. break;
  647. case false:
  648. $is_remote_branch = false;
  649. $_SESSION['PMA_VERSION_REMOTEBRANCH_' . $hash] = false;
  650. break;
  651. case null:
  652. // no remote link for now, but don't cache this as Github is down
  653. $is_remote_branch = false;
  654. break;
  655. }
  656. }
  657. }
  658. if ($commit !== false) {
  659. $author = [
  660. 'name' => '',
  661. 'email' => '',
  662. 'date' => '',
  663. ];
  664. $committer = [
  665. 'name' => '',
  666. 'email' => '',
  667. 'date' => '',
  668. ];
  669. do {
  670. $dataline = array_shift($commit);
  671. $datalinearr = explode(' ', $dataline, 2);
  672. $linetype = $datalinearr[0];
  673. if (in_array($linetype, ['author', 'committer'])) {
  674. $user = $datalinearr[1];
  675. preg_match('/([^<]+)<([^>]+)> ([0-9]+)( [^ ]+)?/', $user, $user);
  676. $user2 = [
  677. 'name' => trim($user[1]),
  678. 'email' => trim($user[2]),
  679. 'date' => date('Y-m-d H:i:s', (int) $user[3]),
  680. ];
  681. if (isset($user[4])) {
  682. $user2['date'] .= $user[4];
  683. }
  684. $$linetype = $user2;
  685. }
  686. } while ($dataline != '');
  687. $message = trim(implode(' ', $commit));
  688. } elseif (isset($commit_json) && isset($commit_json->author) && isset($commit_json->committer) && isset($commit_json->message)) {
  689. $author = [
  690. 'name' => $commit_json->author->name,
  691. 'email' => $commit_json->author->email,
  692. 'date' => $commit_json->author->date,
  693. ];
  694. $committer = [
  695. 'name' => $commit_json->committer->name,
  696. 'email' => $commit_json->committer->email,
  697. 'date' => $commit_json->committer->date,
  698. ];
  699. $message = trim($commit_json->message);
  700. } else {
  701. $this->set('PMA_VERSION_GIT', 0);
  702. return;
  703. }
  704. $this->set('PMA_VERSION_GIT', 1);
  705. $this->set('PMA_VERSION_GIT_COMMITHASH', $hash);
  706. $this->set('PMA_VERSION_GIT_BRANCH', $branch);
  707. $this->set('PMA_VERSION_GIT_MESSAGE', $message);
  708. $this->set('PMA_VERSION_GIT_AUTHOR', $author);
  709. $this->set('PMA_VERSION_GIT_COMMITTER', $committer);
  710. $this->set('PMA_VERSION_GIT_ISREMOTECOMMIT', $is_remote_commit);
  711. $this->set('PMA_VERSION_GIT_ISREMOTEBRANCH', $is_remote_branch);
  712. }
  713. /**
  714. * loads default values from default source
  715. *
  716. * @return boolean success
  717. */
  718. public function loadDefaults(): bool
  719. {
  720. $cfg = [];
  721. if (! @file_exists($this->default_source)) {
  722. $this->error_config_default_file = true;
  723. return false;
  724. }
  725. $canUseErrorReporting = function_exists('error_reporting');
  726. $oldErrorReporting = null;
  727. if ($canUseErrorReporting) {
  728. $oldErrorReporting = error_reporting(0);
  729. }
  730. ob_start();
  731. $GLOBALS['pma_config_loading'] = true;
  732. $eval_result = include $this->default_source;
  733. $GLOBALS['pma_config_loading'] = false;
  734. ob_end_clean();
  735. if ($canUseErrorReporting) {
  736. error_reporting($oldErrorReporting);
  737. }
  738. if ($eval_result === false) {
  739. $this->error_config_default_file = true;
  740. return false;
  741. }
  742. $this->default_source_mtime = filemtime($this->default_source);
  743. $this->default_server = $cfg['Servers'][1];
  744. unset($cfg['Servers']);
  745. $this->default = $cfg;
  746. $this->settings = array_replace_recursive($this->settings, $cfg);
  747. $this->error_config_default_file = false;
  748. return true;
  749. }
  750. /**
  751. * loads configuration from $source, usually the config file
  752. * should be called on object creation
  753. *
  754. * @param string $source config file
  755. *
  756. * @return bool
  757. */
  758. public function load(?string $source = null): bool
  759. {
  760. $this->loadDefaults();
  761. if (null !== $source) {
  762. $this->setSource($source);
  763. }
  764. if (! $this->checkConfigSource()) {
  765. return false;
  766. }
  767. $cfg = [];
  768. /**
  769. * Parses the configuration file, we throw away any errors or
  770. * output.
  771. */
  772. $canUseErrorReporting = function_exists('error_reporting');
  773. $oldErrorReporting = null;
  774. if ($canUseErrorReporting) {
  775. $oldErrorReporting = error_reporting(0);
  776. }
  777. ob_start();
  778. $GLOBALS['pma_config_loading'] = true;
  779. $eval_result = include $this->getSource();
  780. $GLOBALS['pma_config_loading'] = false;
  781. ob_end_clean();
  782. if ($canUseErrorReporting) {
  783. error_reporting($oldErrorReporting);
  784. }
  785. if ($eval_result === false) {
  786. $this->error_config_file = true;
  787. } else {
  788. $this->error_config_file = false;
  789. $this->source_mtime = filemtime($this->getSource());
  790. }
  791. /**
  792. * Ignore keys with / as we do not use these
  793. *
  794. * These can be confusing for user configuration layer as it
  795. * flatten array using / and thus don't see difference between
  796. * $cfg['Export/method'] and $cfg['Export']['method'], while rest
  797. * of thre code uses the setting only in latter form.
  798. *
  799. * This could be removed once we consistently handle both values
  800. * in the functional code as well.
  801. *
  802. * It could use array_filter(...ARRAY_FILTER_USE_KEY), but it's not
  803. * supported on PHP 5.5 and HHVM.
  804. */
  805. $matched_keys = array_filter(
  806. array_keys($cfg),
  807. function ($key) {
  808. return strpos($key, '/') === false;
  809. }
  810. );
  811. $cfg = array_intersect_key($cfg, array_flip($matched_keys));
  812. /**
  813. * Backward compatibility code
  814. */
  815. if (! empty($cfg['DefaultTabTable'])) {
  816. $cfg['DefaultTabTable'] = str_replace(
  817. [
  818. 'tbl_properties.php',
  819. '_properties',
  820. ],
  821. [
  822. 'tbl_sql.php',
  823. '',
  824. ],
  825. $cfg['DefaultTabTable']
  826. );
  827. }
  828. if (! empty($cfg['DefaultTabDatabase'])) {
  829. $cfg['DefaultTabDatabase'] = str_replace(
  830. [
  831. 'db_details.php',
  832. '_details',
  833. ],
  834. [
  835. 'db_sql.php',
  836. '',
  837. ],
  838. $cfg['DefaultTabDatabase']
  839. );
  840. }
  841. $this->settings = array_replace_recursive($this->settings, $cfg);
  842. return true;
  843. }
  844. /**
  845. * Sets the connection collation
  846. *
  847. * @return void
  848. */
  849. private function _setConnectionCollation(): void
  850. {
  851. $collation_connection = $this->get('DefaultConnectionCollation');
  852. if (! empty($collation_connection)
  853. && $collation_connection != $GLOBALS['collation_connection']
  854. ) {
  855. $GLOBALS['dbi']->setCollation($collation_connection);
  856. }
  857. }
  858. /**
  859. * Loads user preferences and merges them with current config
  860. * must be called after control connection has been established
  861. *
  862. * @return void
  863. */
  864. public function loadUserPreferences(): void
  865. {
  866. $userPreferences = new UserPreferences();
  867. // index.php should load these settings, so that phpmyadmin.css.php
  868. // will have everything available in session cache
  869. $server = isset($GLOBALS['server'])
  870. ? $GLOBALS['server']
  871. : (! empty($GLOBALS['cfg']['ServerDefault'])
  872. ? $GLOBALS['cfg']['ServerDefault']
  873. : 0);
  874. $cache_key = 'server_' . $server;
  875. if ($server > 0 && ! defined('PMA_MINIMUM_COMMON')) {
  876. $config_mtime = max($this->default_source_mtime, $this->source_mtime);
  877. // cache user preferences, use database only when needed
  878. if (! isset($_SESSION['cache'][$cache_key]['userprefs'])
  879. || $_SESSION['cache'][$cache_key]['config_mtime'] < $config_mtime
  880. ) {
  881. $prefs = $userPreferences->load();
  882. $_SESSION['cache'][$cache_key]['userprefs']
  883. = $userPreferences->apply($prefs['config_data']);
  884. $_SESSION['cache'][$cache_key]['userprefs_mtime'] = $prefs['mtime'];
  885. $_SESSION['cache'][$cache_key]['userprefs_type'] = $prefs['type'];
  886. $_SESSION['cache'][$cache_key]['config_mtime'] = $config_mtime;
  887. }
  888. } elseif ($server == 0
  889. || ! isset($_SESSION['cache'][$cache_key]['userprefs'])
  890. ) {
  891. $this->set('user_preferences', false);
  892. return;
  893. }
  894. $config_data = $_SESSION['cache'][$cache_key]['userprefs'];
  895. // type is 'db' or 'session'
  896. $this->set(
  897. 'user_preferences',
  898. $_SESSION['cache'][$cache_key]['userprefs_type']
  899. );
  900. $this->set(
  901. 'user_preferences_mtime',
  902. $_SESSION['cache'][$cache_key]['userprefs_mtime']
  903. );
  904. // load config array
  905. $this->settings = array_replace_recursive($this->settings, $config_data);
  906. $GLOBALS['cfg'] = array_replace_recursive($GLOBALS['cfg'], $config_data);
  907. if (defined('PMA_MINIMUM_COMMON')) {
  908. return;
  909. }
  910. // settings below start really working on next page load, but
  911. // changes are made only in index.php so everything is set when
  912. // in frames
  913. // save theme
  914. /** @var ThemeManager $tmanager */
  915. $tmanager = ThemeManager::getInstance();
  916. if ($tmanager->getThemeCookie() || isset($_REQUEST['set_theme'])) {
  917. if ((! isset($config_data['ThemeDefault'])
  918. && $tmanager->theme->getId() != 'original')
  919. || isset($config_data['ThemeDefault'])
  920. && $config_data['ThemeDefault'] != $tmanager->theme->getId()
  921. ) {
  922. // new theme was set in common.inc.php
  923. $this->setUserValue(
  924. null,
  925. 'ThemeDefault',
  926. $tmanager->theme->getId(),
  927. 'original'
  928. );
  929. }
  930. } else {
  931. // no cookie - read default from settings
  932. if ($this->settings['ThemeDefault'] != $tmanager->theme->getId()
  933. && $tmanager->checkTheme($this->settings['ThemeDefault'])
  934. ) {
  935. $tmanager->setActiveTheme($this->settings['ThemeDefault']);
  936. $tmanager->setThemeCookie();
  937. }
  938. }
  939. // save language
  940. if ($this->issetCookie('pma_lang') || isset($_POST['lang'])) {
  941. if ((! isset($config_data['lang'])
  942. && $GLOBALS['lang'] != 'en')
  943. || isset($config_data['lang'])
  944. && $GLOBALS['lang'] != $config_data['lang']
  945. ) {
  946. $this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en');
  947. }
  948. } else {
  949. // read language from settings
  950. if (isset($config_data['lang'])) {
  951. $language = LanguageManager::getInstance()->getLanguage(
  952. $config_data['lang']
  953. );
  954. if ($language !== false) {
  955. $language->activate();
  956. $this->setCookie('pma_lang', $language->getCode());
  957. }
  958. }
  959. }
  960. // set connection collation
  961. $this->_setConnectionCollation();
  962. }
  963. /**
  964. * Sets config value which is stored in user preferences (if available)
  965. * or in a cookie.
  966. *
  967. * If user preferences are not yet initialized, option is applied to
  968. * global config and added to a update queue, which is processed
  969. * by {@link loadUserPreferences()}
  970. *
  971. * @param string|null $cookie_name can be null
  972. * @param string $cfg_path configuration path
  973. * @param mixed $new_cfg_value new value
  974. * @param mixed $default_value default value
  975. *
  976. * @return true|Message
  977. */
  978. public function setUserValue(
  979. ?string $cookie_name,
  980. string $cfg_path,
  981. $new_cfg_value,
  982. $default_value = null
  983. ) {
  984. $userPreferences = new UserPreferences();
  985. $result = true;
  986. // use permanent user preferences if possible
  987. $prefs_type = $this->get('user_preferences');
  988. if ($prefs_type) {
  989. if ($default_value === null) {
  990. $default_value = Core::arrayRead($cfg_path, $this->default);
  991. }
  992. $result = $userPreferences->persistOption($cfg_path, $new_cfg_value, $default_value);
  993. }
  994. if ($prefs_type != 'db' && $cookie_name) {
  995. // fall back to cookies
  996. if ($default_value === null) {
  997. $default_value = Core::arrayRead($cfg_path, $this->settings);
  998. }
  999. $this->setCookie($cookie_name, $new_cfg_value, $default_value);
  1000. }
  1001. Core::arrayWrite($cfg_path, $GLOBALS['cfg'], $new_cfg_value);
  1002. Core::arrayWrite($cfg_path, $this->settings, $new_cfg_value);
  1003. return $result;
  1004. }
  1005. /**
  1006. * Reads value stored by {@link setUserValue()}
  1007. *
  1008. * @param string $cookie_name cookie name
  1009. * @param mixed $cfg_value config value
  1010. *
  1011. * @return mixed
  1012. */
  1013. public function getUserValue(string $cookie_name, $cfg_value)
  1014. {
  1015. $cookie_exists = isset($_COOKIE) && ! empty($this->getCookie($cookie_name));
  1016. $prefs_type = $this->get('user_preferences');
  1017. if ($prefs_type == 'db') {
  1018. // permanent user preferences value exists, remove cookie
  1019. if ($cookie_exists) {
  1020. $this->removeCookie($cookie_name);
  1021. }
  1022. } elseif ($cookie_exists) {
  1023. return $this->getCookie($cookie_name);
  1024. }
  1025. // return value from $cfg array
  1026. return $cfg_value;
  1027. }
  1028. /**
  1029. * set source
  1030. *
  1031. * @param string $source source
  1032. *
  1033. * @return void
  1034. */
  1035. public function setSource(string $source): void
  1036. {
  1037. $this->source = trim($source);
  1038. }
  1039. /**
  1040. * check config source
  1041. *
  1042. * @return boolean whether source is valid or not
  1043. */
  1044. public function checkConfigSource(): bool
  1045. {
  1046. if (! $this->getSource()) {
  1047. // no configuration file set at all
  1048. return false;
  1049. }
  1050. if (! @file_exists($this->getSource())) {
  1051. $this->source_mtime = 0;
  1052. return false;
  1053. }
  1054. if (! @is_readable($this->getSource())) {
  1055. // manually check if file is readable
  1056. // might be bug #3059806 Supporting running from CIFS/Samba shares
  1057. $contents = false;
  1058. $handle = @fopen($this->getSource(), 'r');
  1059. if ($handle !== false) {
  1060. $contents = @fread($handle, 1); // reading 1 byte is enough to test
  1061. fclose($handle);
  1062. }
  1063. if ($contents === false) {
  1064. $this->source_mtime = 0;
  1065. Core::fatalError(
  1066. sprintf(
  1067. function_exists('__')
  1068. ? __('Existing configuration file (%s) is not readable.')
  1069. : 'Existing configuration file (%s) is not readable.',
  1070. $this->getSource()
  1071. )
  1072. );
  1073. return false;
  1074. }
  1075. }
  1076. return true;
  1077. }
  1078. /**
  1079. * verifies the permissions on config file (if asked by configuration)
  1080. * (must be called after config.inc.php has been merged)
  1081. *
  1082. * @return void
  1083. */
  1084. public function checkPermissions(): void
  1085. {
  1086. // Check for permissions (on platforms that support it):
  1087. if ($this->get('CheckConfigurationPermissions') && @file_exists($this->getSource())) {
  1088. $perms = @fileperms($this->getSource());
  1089. if (! ($perms === false) && ($perms & 2)) {
  1090. // This check is normally done after loading configuration
  1091. $this->checkWebServerOs();
  1092. if ($this->get('PMA_IS_WINDOWS') == 0) {
  1093. $this->source_mtime = 0;
  1094. Core::fatalError(
  1095. __(
  1096. 'Wrong permissions on configuration file, '
  1097. . 'should not be world writable!'
  1098. )
  1099. );
  1100. }
  1101. }
  1102. }
  1103. }
  1104. /**
  1105. * Checks for errors
  1106. * (must be called after config.inc.php has been merged)
  1107. *
  1108. * @return void
  1109. */
  1110. public function checkErrors(): void
  1111. {
  1112. if ($this->error_config_default_file) {
  1113. Core::fatalError(
  1114. sprintf(
  1115. __('Could not load default configuration from: %1$s'),
  1116. $this->default_source
  1117. )
  1118. );
  1119. }
  1120. if ($this->error_config_file) {
  1121. $error = '[strong]' . __('Failed to read configuration file!') . '[/strong]'
  1122. . '[br][br]'
  1123. . __(
  1124. 'This usually means there is a syntax error in it, '
  1125. . 'please check any errors shown below.'
  1126. )
  1127. . '[br][br]'
  1128. . '[conferr]';
  1129. trigger_error($error, E_USER_ERROR);
  1130. }
  1131. }
  1132. /**
  1133. * returns specific config setting
  1134. *
  1135. * @param string $setting config setting
  1136. *
  1137. * @return mixed value
  1138. */
  1139. public function get(string $setting)
  1140. {
  1141. if (isset($this->settings[$setting])) {
  1142. return $this->settings[$setting];
  1143. }
  1144. return null;
  1145. }
  1146. /**
  1147. * sets configuration variable
  1148. *
  1149. * @param string $setting configuration option
  1150. * @param mixed $value new value for configuration option
  1151. *
  1152. * @return void
  1153. */
  1154. public function set(string $setting, $value): void
  1155. {
  1156. if (! isset($this->settings[$setting])
  1157. || $this->settings[$setting] !== $value
  1158. ) {
  1159. $this->settings[$setting] = $value;
  1160. $this->set_mtime = time();
  1161. }
  1162. }
  1163. /**
  1164. * returns source for current config
  1165. *
  1166. * @return string config source
  1167. */
  1168. public function getSource(): string
  1169. {
  1170. return $this->source;
  1171. }
  1172. /**
  1173. * returns a unique value to force a CSS reload if either the config
  1174. * or the theme changes
  1175. *
  1176. * @return int Summary of unix timestamps, to be unique on theme parameters
  1177. * change
  1178. */
  1179. public function getThemeUniqueValue(): int
  1180. {
  1181. return (int) (
  1182. $this->source_mtime +
  1183. $this->default_source_mtime +
  1184. $this->get('user_preferences_mtime') +
  1185. $GLOBALS['PMA_Theme']->mtime_info +
  1186. $GLOBALS['PMA_Theme']->filesize_info
  1187. );
  1188. }
  1189. /**
  1190. * checks if upload is enabled
  1191. *
  1192. * @return void
  1193. */
  1194. public function checkUpload(): void
  1195. {
  1196. if (! ini_get('file_uploads')) {
  1197. $this->set('enable_upload', false);
  1198. return;
  1199. }
  1200. $this->set('enable_upload', true);
  1201. // if set "php_admin_value file_uploads Off" in httpd.conf
  1202. // ini_get() also returns the string "Off" in this case:
  1203. if ('off' == strtolower(ini_get('file_uploads'))) {
  1204. $this->set('enable_upload', false);
  1205. }
  1206. }
  1207. /**
  1208. * Maximum upload size as limited by PHP
  1209. * Used with permission from Moodle (https://moodle.org/) by Martin Dougiamas
  1210. *
  1211. * this section generates $max_upload_size in bytes
  1212. *
  1213. * @return void
  1214. */
  1215. public function checkUploadSize(): void
  1216. {
  1217. if (! $filesize = ini_get('upload_max_filesize')) {
  1218. $filesize = "5M";
  1219. }
  1220. if ($postsize = ini_get('post_max_size')) {
  1221. $this->set(
  1222. 'max_upload_size',
  1223. min(Core::getRealSize($filesize), Core::getRealSize($postsize))
  1224. );
  1225. } else {
  1226. $this->set('max_upload_size', Core::getRealSize($filesize));
  1227. }
  1228. }
  1229. /**
  1230. * Checks if protocol is https
  1231. *
  1232. * This function checks if the https protocol on the active connection.
  1233. *
  1234. * @return bool
  1235. */
  1236. public function isHttps(): bool
  1237. {
  1238. if (null !== $this->get('is_https')) {
  1239. return $this->get('is_https');
  1240. }
  1241. $url = $this->get('PmaAbsoluteUri');
  1242. $is_https = false;
  1243. if (! empty($url) && parse_url($url, PHP_URL_SCHEME) === 'https') {
  1244. $is_https = true;
  1245. } elseif (strtolower(Core::getenv('HTTP_SCHEME')) == 'https') {
  1246. $is_https = true;
  1247. } elseif (strtolower(Core::getenv('HTTPS')) == 'on') {
  1248. $is_https = true;
  1249. } elseif (substr(strtolower(Core::getenv('REQUEST_URI')), 0, 6) == 'https:') {
  1250. $is_https = true;
  1251. } elseif (strtolower(Core::getenv('HTTP_HTTPS_FROM_LB')) == 'on') {
  1252. // A10 Networks load balancer
  1253. $is_https = true;
  1254. } elseif (strtolower(Core::getenv('HTTP_FRONT_END_HTTPS')) == 'on') {
  1255. $is_https = true;
  1256. } elseif (strtolower(Core::getenv('HTTP_X_FORWARDED_PROTO')) == 'https') {
  1257. $is_https = true;
  1258. } elseif (strtolower(Core::getenv('HTTP_CLOUDFRONT_FORWARDED_PROTO')) === 'https') {
  1259. // Amazon CloudFront, issue #15621
  1260. $is_https = true;
  1261. } elseif (Util::getProtoFromForwardedHeader(Core::getenv('HTTP_FORWARDED')) === 'https') {
  1262. // RFC 7239 Forwarded header
  1263. $is_https = true;
  1264. } elseif (Core::getenv('SERVER_PORT') == 443) {
  1265. $is_https = true;
  1266. }
  1267. $this->set('is_https', $is_https);
  1268. return $is_https;
  1269. }
  1270. /**
  1271. * Get phpMyAdmin root path
  1272. *
  1273. * @return string
  1274. */
  1275. public function getRootPath(): string
  1276. {
  1277. static $cookie_path = null;
  1278. if (null !== $cookie_path && ! defined('TESTSUITE')) {
  1279. return $cookie_path;
  1280. }
  1281. $url = $this->get('PmaAbsoluteUri');
  1282. if (! empty($url)) {
  1283. $path = parse_url($url, PHP_URL_PATH);
  1284. if (! empty($path)) {
  1285. if (substr($path, -1) != '/') {
  1286. return $path . '/';
  1287. }
  1288. return $path;
  1289. }
  1290. }
  1291. $parsed_url = parse_url($GLOBALS['PMA_PHP_SELF']);
  1292. $parts = explode(
  1293. '/',
  1294. rtrim(str_replace('\\', '/', $parsed_url['path']), '/')
  1295. );
  1296. /* Remove filename */
  1297. if (substr($parts[count($parts) - 1], -4) == '.php') {
  1298. $parts = array_slice($parts, 0, count($parts) - 1);
  1299. }
  1300. /* Remove extra path from javascript calls */
  1301. if (defined('PMA_PATH_TO_BASEDIR')) {
  1302. $parts = array_slice($parts, 0, count($parts) - 1);
  1303. }
  1304. $parts[] = '';
  1305. return implode('/', $parts);
  1306. }
  1307. /**
  1308. * enables backward compatibility
  1309. *
  1310. * @return void
  1311. */
  1312. public function enableBc(): void
  1313. {
  1314. $GLOBALS['cfg'] = $this->settings;
  1315. $GLOBALS['default_server'] = $this->default_server;
  1316. unset($this->default_server);
  1317. $GLOBALS['is_upload'] = $this->get('enable_upload');
  1318. $GLOBALS['max_upload_size'] = $this->get('max_upload_size');
  1319. $GLOBALS['is_https'] = $this->get('is_https');
  1320. $defines = [
  1321. 'PMA_VERSION',
  1322. 'PMA_MAJOR_VERSION',
  1323. 'PMA_THEME_VERSION',
  1324. 'PMA_THEME_GENERATION',
  1325. 'PMA_IS_WINDOWS',
  1326. 'PMA_IS_GD2',
  1327. 'PMA_USR_OS',
  1328. 'PMA_USR_BROWSER_VER',
  1329. 'PMA_USR_BROWSER_AGENT',
  1330. ];
  1331. foreach ($defines as $define) {
  1332. if (! defined($define)) {
  1333. define($define, $this->get($define));
  1334. }
  1335. }
  1336. }
  1337. /**
  1338. * removes cookie
  1339. *
  1340. * @param string $cookieName name of cookie to remove
  1341. *
  1342. * @return boolean result of setcookie()
  1343. */
  1344. public function removeCookie(string $cookieName): bool
  1345. {
  1346. $httpCookieName = $this->getCookieName($cookieName);
  1347. if ($this->issetCookie($cookieName)) {
  1348. unset($_COOKIE[$httpCookieName]);
  1349. }
  1350. if (defined('TESTSUITE')) {
  1351. return true;
  1352. }
  1353. return setcookie(
  1354. $httpCookieName,
  1355. '',
  1356. time() - 3600,
  1357. $this->getRootPath(),
  1358. '',
  1359. $this->isHttps()
  1360. );
  1361. }
  1362. /**
  1363. * sets cookie if value is different from current cookie value,
  1364. * or removes if value is equal to default
  1365. *
  1366. * @param string $cookie name of cookie to remove
  1367. * @param mixed $value new cookie value
  1368. * @param string $default default value
  1369. * @param int $validity validity of cookie in seconds (default is one month)
  1370. * @param bool $httponly whether cookie is only for HTTP (and not for scripts)
  1371. *
  1372. * @return boolean result of setcookie()
  1373. */
  1374. public function setCookie(
  1375. string $cookie,
  1376. $value,
  1377. ?string $default = null,
  1378. ?int $validity = null,
  1379. bool $httponly = true
  1380. ): bool {
  1381. if (strlen($value) > 0 && null !== $default && $value === $default
  1382. ) {
  1383. // default value is used
  1384. if ($this->issetCookie($cookie)) {
  1385. // remove cookie
  1386. return $this->removeCookie($cookie);
  1387. }
  1388. return false;
  1389. }
  1390. if (strlen($value) === 0 && $this->issetCookie($cookie)) {
  1391. // remove cookie, value is empty
  1392. return $this->removeCookie($cookie);
  1393. }
  1394. $httpCookieName = $this->getCookieName($cookie);
  1395. if (! $this->issetCookie($cookie) || $this->getCookie($cookie) !== $value) {
  1396. // set cookie with new value
  1397. /* Calculate cookie validity */
  1398. if ($validity === null) {
  1399. /* Valid for one month */
  1400. $validity = time() + 2592000;
  1401. } elseif ($validity == 0) {
  1402. /* Valid for session */
  1403. $validity = 0;
  1404. } else {
  1405. $validity = time() + $validity;
  1406. }
  1407. if (defined('TESTSUITE')) {
  1408. $_COOKIE[$httpCookieName] = $value;
  1409. return true;
  1410. }
  1411. return setcookie(
  1412. $httpCookieName,
  1413. $value,
  1414. $validity,
  1415. $this->getRootPath(),
  1416. '',
  1417. $this->isHttps(),
  1418. $httponly
  1419. );
  1420. }
  1421. // cookie has already $value as value
  1422. return true;
  1423. }
  1424. /**
  1425. * get cookie
  1426. *
  1427. * @param string $cookieName The name of the cookie to get
  1428. *
  1429. * @return mixed result of getCookie()
  1430. */
  1431. public function getCookie(string $cookieName)
  1432. {
  1433. if (isset($_COOKIE[$this->getCookieName($cookieName)])) {
  1434. return $_COOKIE[$this->getCookieName($cookieName)];
  1435. } else {
  1436. return null;
  1437. }
  1438. }
  1439. /**
  1440. * Get the real cookie name
  1441. *
  1442. * @param string $cookieName The name of the cookie
  1443. * @return string
  1444. */
  1445. public function getCookieName(string $cookieName): string
  1446. {
  1447. return $cookieName . ( ($this->isHttps()) ? '_https' : '' );
  1448. }
  1449. /**
  1450. * isset cookie
  1451. *
  1452. * @param string $cookieName The name of the cookie to check
  1453. *
  1454. * @return bool result of issetCookie()
  1455. */
  1456. public function issetCookie(string $cookieName): bool
  1457. {
  1458. return isset($_COOKIE[$this->getCookieName($cookieName)]);
  1459. }
  1460. /**
  1461. * Error handler to catch fatal errors when loading configuration
  1462. * file
  1463. *
  1464. * @return void
  1465. */
  1466. public static function fatalErrorHandler(): void
  1467. {
  1468. if (! isset($GLOBALS['pma_config_loading'])
  1469. || ! $GLOBALS['pma_config_loading']
  1470. ) {
  1471. return;
  1472. }
  1473. $error = error_get_last();
  1474. if ($error === null) {
  1475. return;
  1476. }
  1477. Core::fatalError(
  1478. sprintf(
  1479. 'Failed to load phpMyAdmin configuration (%s:%s): %s',
  1480. Error::relPath($error['file']),
  1481. $error['line'],
  1482. $error['message']
  1483. )
  1484. );
  1485. }
  1486. /**
  1487. * Wrapper for footer/header rendering
  1488. *
  1489. * @param string $filename File to check and render
  1490. * @param string $id Div ID
  1491. *
  1492. * @return string
  1493. */
  1494. private static function _renderCustom(string $filename, string $id): string
  1495. {
  1496. $retval = '';
  1497. if (@file_exists($filename)) {
  1498. $retval .= '<div id="' . $id . '">';
  1499. ob_start();
  1500. include $filename;
  1501. $retval .= ob_get_contents();
  1502. ob_end_clean();
  1503. $retval .= '</div>';
  1504. }
  1505. return $retval;
  1506. }
  1507. /**
  1508. * Renders user configured footer
  1509. *
  1510. * @return string
  1511. */
  1512. public static function renderFooter(): string
  1513. {
  1514. return self::_renderCustom(CUSTOM_FOOTER_FILE, 'pma_footer');
  1515. }
  1516. /**
  1517. * Renders user configured footer
  1518. *
  1519. * @return string
  1520. */
  1521. public static function renderHeader(): string
  1522. {
  1523. return self::_renderCustom(CUSTOM_HEADER_FILE, 'pma_header');
  1524. }
  1525. /**
  1526. * Returns temporary dir path
  1527. *
  1528. * @param string $name Directory name
  1529. *
  1530. * @return string|null
  1531. */
  1532. public function getTempDir(string $name): ?string
  1533. {
  1534. static $temp_dir = [];
  1535. if (isset($temp_dir[$name]) && ! defined('TESTSUITE')) {
  1536. return $temp_dir[$name];
  1537. }
  1538. $path = $this->get('TempDir');
  1539. if (empty($path)) {
  1540. $path = null;
  1541. } else {
  1542. $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
  1543. if (! @is_dir($path)) {
  1544. @mkdir($path, 0770, true);
  1545. }
  1546. if (! @is_dir($path) || ! @is_writable($path)) {
  1547. $path = null;
  1548. }
  1549. }
  1550. $temp_dir[$name] = $path;
  1551. return $path;
  1552. }
  1553. /**
  1554. * Returns temporary directory
  1555. *
  1556. * @return string|null
  1557. */
  1558. public function getUploadTempDir(): ?string
  1559. {
  1560. // First try configured temp dir
  1561. // Fallback to PHP upload_tmp_dir
  1562. $dirs = [
  1563. $this->getTempDir('upload'),
  1564. ini_get('upload_tmp_dir'),
  1565. sys_get_temp_dir(),
  1566. ];
  1567. foreach ($dirs as $dir) {
  1568. if (! empty($dir) && @is_writable($dir)) {
  1569. return realpath($dir);
  1570. }
  1571. }
  1572. return null;
  1573. }
  1574. /**
  1575. * Selects server based on request parameters.
  1576. *
  1577. * @return integer
  1578. */
  1579. public function selectServer(): int
  1580. {
  1581. $request = empty($_REQUEST['server']) ? 0 : $_REQUEST['server'];
  1582. /**
  1583. * Lookup server by name
  1584. * (see FAQ 4.8)
  1585. */
  1586. if (! is_numeric($request)) {
  1587. foreach ($this->settings['Servers'] as $i => $server) {
  1588. $verboseToLower = mb_strtolower($server['verbose']);
  1589. $serverToLower = mb_strtolower($request);
  1590. if ($server['host'] == $request
  1591. || $server['verbose'] == $request
  1592. || $verboseToLower == $serverToLower
  1593. || md5($verboseToLower) === $serverToLower
  1594. ) {
  1595. $request = $i;
  1596. break;
  1597. }
  1598. }
  1599. if (is_string($request)) {
  1600. $request = 0;
  1601. }
  1602. }
  1603. /**
  1604. * If no server is selected, make sure that $this->settings['Server'] is empty (so
  1605. * that nothing will work), and skip server authentication.
  1606. * We do NOT exit here, but continue on without logging into any server.
  1607. * This way, the welcome page will still come up (with no server info) and
  1608. * present a choice of servers in the case that there are multiple servers
  1609. * and '$this->settings['ServerDefault'] = 0' is set.
  1610. */
  1611. if (is_numeric($request) && ! empty($request) && ! empty($this->settings['Servers'][$request])) {
  1612. $server = $request;
  1613. $this->settings['Server'] = $this->settings['Servers'][$server];
  1614. } else {
  1615. if (! empty($this->settings['Servers'][$this->settings['ServerDefault']])) {
  1616. $server = $this->settings['ServerDefault'];
  1617. $this->settings['Server'] = $this->settings['Servers'][$server];
  1618. } else {
  1619. $server = 0;
  1620. $this->settings['Server'] = [];
  1621. }
  1622. }
  1623. return (int) $server;
  1624. }
  1625. /**
  1626. * Checks whether Servers configuration is valid and possibly apply fixups.
  1627. *
  1628. * @return void
  1629. */
  1630. public function checkServers(): void
  1631. {
  1632. // Do we have some server?
  1633. if (! isset($this->settings['Servers']) || count($this->settings['Servers']) === 0) {
  1634. // No server => create one with defaults
  1635. $this->settings['Servers'] = [1 => $this->default_server];
  1636. } else {
  1637. // We have server(s) => apply default configuration
  1638. $new_servers = [];
  1639. foreach ($this->settings['Servers'] as $server_index => $each_server) {
  1640. // Detect wrong configuration
  1641. if (! is_int($server_index) || $server_index < 1) {
  1642. trigger_error(
  1643. sprintf(__('Invalid server index: %s'), $server_index),
  1644. E_USER_ERROR
  1645. );
  1646. }
  1647. $each_server = array_merge($this->default_server, $each_server);
  1648. // Final solution to bug #582890
  1649. // If we are using a socket connection
  1650. // and there is nothing in the verbose server name
  1651. // or the host field, then generate a name for the server
  1652. // in the form of "Server 2", localized of course!
  1653. if (empty($each_server['host']) && empty($each_server['verbose'])) {
  1654. $each_server['verbose'] = sprintf(__('Server %d'), $server_index);
  1655. }
  1656. $new_servers[$server_index] = $each_server;
  1657. }
  1658. $this->settings['Servers'] = $new_servers;
  1659. }
  1660. }
  1661. }
  1662. if (! defined('TESTSUITE')) {
  1663. register_shutdown_function([Config::class, 'fatalErrorHandler']);
  1664. }