3 declare(strict_types
=1);
5 namespace PhpMyAdmin\Tests
;
9 use PhpMyAdmin\Current
;
10 use PhpMyAdmin\Dbal\DatabaseInterface
;
11 use PhpMyAdmin\Http\ServerRequest
;
12 use PhpMyAdmin\ResponseRenderer
;
14 use PHPUnit\Framework\Attributes\CoversClass
;
15 use PHPUnit\Framework\Attributes\DataProvider
;
16 use PHPUnit\Framework\Attributes\Group
;
19 use function _pgettext
;
22 use function serialize
;
23 use function str_repeat
;
25 #[CoversClass(Core::class)]
26 class CoreTest
extends AbstractTestCase
29 * Setup for test cases
31 protected function setUp(): void
37 DatabaseInterface
::$instance = $this->createDatabaseInterface();
38 Current
::$database = '';
40 Config
::getInstance()->set('URLQueryEncryption', false);
44 * Test for Core::arrayRead
46 public function testArrayRead(): void
51 'arr' => ['val1', 'val2', 'val3'],
52 'sarr' => ['arr1' => [1, 2, 3], [3, ['a', 'b', 'c'], 4]],
56 Core
::arrayRead('int', $arr),
61 Core
::arrayRead('str', $arr),
66 Core
::arrayRead('arr/0', $arr),
71 Core
::arrayRead('arr/1', $arr),
76 Core
::arrayRead('arr/2', $arr),
81 Core
::arrayRead('sarr/arr1/0', $arr),
82 $arr['sarr']['arr1'][0],
86 Core
::arrayRead('sarr/arr1/1', $arr),
87 $arr['sarr']['arr1'][1],
91 Core
::arrayRead('sarr/arr1/2', $arr),
92 $arr['sarr']['arr1'][2],
96 Core
::arrayRead('sarr/0/0', $arr),
101 Core
::arrayRead('sarr/0/1', $arr),
106 Core
::arrayRead('sarr/0/1/2', $arr),
107 $arr['sarr'][0][1][2],
111 Core
::arrayRead('sarr/not_exiting/1', $arr),
115 Core
::arrayRead('sarr/not_exiting/1', $arr, 0),
120 Core
::arrayRead('sarr/not_exiting/1', $arr, 'default_val'),
126 * Test for Core::arrayWrite
128 public function testArrayWrite(): void
133 'arr' => ['val1', 'val2', 'val3'],
134 'sarr' => ['arr1' => [1, 2, 3], [3, ['a', 'b', 'c'], 4]],
137 Core
::arrayWrite('int', $arr, 5);
138 self
::assertSame($arr['int'], 5);
140 Core
::arrayWrite('str', $arr, '_str');
141 self
::assertSame($arr['str'], '_str');
143 Core
::arrayWrite('arr/0', $arr, 'val_arr_0');
144 self
::assertSame($arr['arr'][0], 'val_arr_0');
146 Core
::arrayWrite('arr/1', $arr, 'val_arr_1');
147 self
::assertSame($arr['arr'][1], 'val_arr_1');
149 Core
::arrayWrite('arr/2', $arr, 'val_arr_2');
150 self
::assertSame($arr['arr'][2], 'val_arr_2');
152 Core
::arrayWrite('sarr/arr1/0', $arr, 'val_sarr_arr_0');
153 self
::assertSame($arr['sarr']['arr1'][0], 'val_sarr_arr_0');
155 Core
::arrayWrite('sarr/arr1/1', $arr, 'val_sarr_arr_1');
156 self
::assertSame($arr['sarr']['arr1'][1], 'val_sarr_arr_1');
158 Core
::arrayWrite('sarr/arr1/2', $arr, 'val_sarr_arr_2');
159 self
::assertSame($arr['sarr']['arr1'][2], 'val_sarr_arr_2');
161 Core
::arrayWrite('sarr/0/0', $arr, 5);
162 self
::assertSame($arr['sarr'][0][0], 5);
164 Core
::arrayWrite('sarr/0/1/0', $arr, 'e');
165 self
::assertSame($arr['sarr'][0][1][0], 'e');
167 Core
::arrayWrite('sarr/not_existing/1', $arr, 'some_val');
168 self
::assertSame($arr['sarr']['not_existing'][1], 'some_val');
170 Core
::arrayWrite('sarr/0/2', $arr, null);
171 self
::assertNull($arr['sarr'][0][2]);
175 * Test for Core::arrayRemove
177 public function testArrayRemove(): void
182 'arr' => ['val1', 'val2', 'val3'],
183 'sarr' => ['arr1' => [1, 2, 3], [3, ['a', 'b', 'c'], 4]],
186 Core
::arrayRemove('int', $arr);
187 self
::assertArrayNotHasKey('int', $arr);
189 Core
::arrayRemove('str', $arr);
190 self
::assertArrayNotHasKey('str', $arr);
192 Core
::arrayRemove('arr/0', $arr);
193 self
::assertArrayNotHasKey(0, $arr['arr']);
195 Core
::arrayRemove('arr/1', $arr);
196 self
::assertArrayNotHasKey(1, $arr['arr']);
198 Core
::arrayRemove('arr/2', $arr);
199 self
::assertArrayNotHasKey('arr', $arr);
202 Core
::arrayRemove('sarr/not_existing/1', $arr);
203 self
::assertSame($tmpArr, $arr);
205 Core
::arrayRemove('sarr/arr1/0', $arr);
206 self
::assertArrayNotHasKey(0, $arr['sarr']['arr1']);
208 Core
::arrayRemove('sarr/arr1/1', $arr);
209 self
::assertArrayNotHasKey(1, $arr['sarr']['arr1']);
211 Core
::arrayRemove('sarr/arr1/2', $arr);
212 self
::assertArrayNotHasKey('arr1', $arr['sarr']);
214 Core
::arrayRemove('sarr/0/0', $arr);
215 self
::assertArrayNotHasKey(0, $arr['sarr'][0]);
217 Core
::arrayRemove('sarr/0/1/0', $arr);
218 self
::assertArrayNotHasKey(0, $arr['sarr'][0][1]);
220 Core
::arrayRemove('sarr/0/1/1', $arr);
221 self
::assertArrayNotHasKey(1, $arr['sarr'][0][1]);
223 Core
::arrayRemove('sarr/0/1/2', $arr);
224 self
::assertArrayNotHasKey(1, $arr['sarr'][0]);
226 Core
::arrayRemove('sarr/0/2', $arr);
228 self
::assertEmpty($arr);
232 * Test for Core::checkPageValidity
234 * @param string $page Page
235 * @param bool $expected Expected value
237 #[DataProvider('providerTestGotoNowhere')]
238 public function testGotoNowhere(string $page, bool $expected): void
240 self
::assertSame($expected, Core
::checkPageValidity($page));
244 * Data provider for testGotoNowhere
246 * @return array<array{string, bool}>
248 public static function providerTestGotoNowhere(): array
252 ['shell.php', false],
253 ['index.php?sql.php&test=true', true],
254 ['shell.php?sql.php&test=true', false],
255 ['index.php%3Fsql.php%26test%3Dtrue', true],
256 ['shell.php%3Fsql.php%26test%3Dtrue', false],
261 * Test for Core::getRealSize
263 * @param string $size Size
264 * @param int $expected Expected value
266 #[DataProvider('providerTestGetRealSize')]
267 #[Group('32bit-incompatible')]
268 public function testGetRealSize(string $size, int $expected): void
270 self
::assertSame($expected, Core
::getRealSize($size));
274 * Data provider for testGetRealSize
276 * @return array<array{string, int}>
278 public static function providerTestGetRealSize(): array
283 ['1024k', 1024 * 1024],
284 ['8m', 8 * 1024 * 1024],
285 ['12gb', 12 * 1024 * 1024 * 1024],
287 ['8000m', 8 * 1000 * 1024 * 1024],
288 ['8G', 8 * 1024 * 1024 * 1024],
290 ['2048K', 2048 * 1024],
291 ['2048K', 2048 * 1024],
292 ['102400K', 102400 * 1024],
297 * Test for Core::getPHPDocLink
299 public function testGetPHPDocLink(): void
301 $lang = _pgettext('PHP documentation language', 'en');
303 Core
::getPHPDocLink('function'),
304 'index.php?route=/url&url=https%3A%2F%2Fwww.php.net%2Fmanual%2F'
305 . $lang . '%2Ffunction',
310 * Test for Core::linkURL
312 * @param string $link URL where to go
313 * @param string $url Expected value
315 #[DataProvider('providerTestLinkURL')]
316 public function testLinkURL(string $link, string $url): void
318 self
::assertSame(Core
::linkURL($link), $url);
322 * Data provider for testLinkURL
324 * @return array<array{string, string}>
326 public static function providerTestLinkURL(): array
329 ['https://wiki.phpmyadmin.net', 'index.php?route=/url&url=https%3A%2F%2Fwiki.phpmyadmin.net'],
330 ['https://wiki.phpmyadmin.net', 'index.php?route=/url&url=https%3A%2F%2Fwiki.phpmyadmin.net'],
331 ['wiki.phpmyadmin.net', 'wiki.phpmyadmin.net'],
332 ['index.php?db=phpmyadmin', 'index.php?db=phpmyadmin'],
336 #[DataProvider('provideTestIsAllowedDomain')]
337 public function testIsAllowedDomain(string $url, bool $expected): void
339 $_SERVER['SERVER_NAME'] = 'server.local';
342 Core
::isAllowedDomain($url),
347 * @return array<int, array<int, bool|string>>
348 * @psalm-return list<array{string, bool}>
350 public static function provideTestIsAllowedDomain(): array
355 ['https://www.phpmyadmin.net/', true],
356 ['https://www.phpmyadmin.net:123/', false],
357 ['http://duckduckgo.com\\@github.com', false],
358 ['https://user:pass@github.com:123/', false],
359 ['https://user:pass@github.com/', false],
360 ['https://server.local/', true],
361 ['./relative/', false],
362 ['//wiki.phpmyadmin.net', true],
363 ['//www.phpmyadmin.net', true],
364 ['//phpmyadmin.net', true],
365 ['//demo.phpmyadmin.net', true],
366 ['//docs.phpmyadmin.net', true],
367 ['//dev.mysql.com', true],
368 ['//bugs.mysql.com', true],
369 ['//mariadb.org', true],
370 ['//mariadb.com', true],
372 ['//www.php.net', true],
373 ['//github.com', true],
374 ['//www.github.com', true],
375 ['//www.percona.com', true],
376 ['//mysqldatabaseadministration.blogspot.com', true],
381 * Test for unserializing
383 * @param string $data Serialized data
384 * @param mixed $expected Expected result
386 #[DataProvider('provideTestSafeUnserialize')]
387 public function testSafeUnserialize(string $data, mixed $expected): void
391 Core
::safeUnserialize($data),
398 * @return array<array{string, mixed}>
400 public static function provideTestSafeUnserialize(): array
403 ['s:6:"foobar";', 'foobar'],
406 ['O:1:"a":1:{s:5:"value";s:3:"100";}', null],
407 ['O:8:"stdClass":1:{s:5:"field";O:8:"stdClass":0:{}}', null],
409 'a:2:{i:0;s:90:"1234567890;a3456789012345678901234567890123456789012'
410 . '34567890123456789012345678901234567890";i:1;O:8:"stdClass":0:{}}',
413 [serialize([1, 2, 3]), [1, 2, 3]],
414 [serialize('string""'), 'string""'],
415 [serialize(['foo' => 'bar']), ['foo' => 'bar']],
416 [serialize(['1', new stdClass(), '2']), null],
421 * Test for MySQL host sanitizing
423 * @param string $host Test host name
424 * @param string $expected Expected result
426 #[DataProvider('provideTestSanitizeMySQLHost')]
427 public function testSanitizeMySQLHost(string $host, string $expected): void
431 Core
::sanitizeMySQLHost($host),
438 * @return array<array{string, string}>
440 public static function provideTestSanitizeMySQLHost(): array
443 ['p:foo.bar', 'foo.bar'],
444 ['p:p:foo.bar', 'foo.bar'],
445 ['bar.baz', 'bar.baz'],
446 ['P:example.com', 'example.com'],
451 * Test for replacing dots.
453 public function testReplaceDots(): void
456 Core
::securePath('../../../etc/passwd'),
460 Core
::securePath('/var/www/../phpmyadmin'),
461 '/var/www/./phpmyadmin',
464 Core
::securePath('./path/with..dots/../../file..php'),
465 './path/with.dots/././file.php',
470 * Test for Core::warnMissingExtension
472 public function testMissingExtensionFatal(): void
475 ResponseRenderer
::getInstance()->setAjax(false);
478 $warn = 'The <a href="' . Core
::getPHPDocLink('book.' . $ext . '.php')
479 . '" target="Documentation"><em>' . $ext
480 . '</em></a> extension is missing. Please check your PHP configuration.';
482 $this->expectExceptionMessage($warn);
484 Core
::warnMissingExtension($ext, true);
488 * Test for Core::warnMissingExtension
490 public function testMissingExtensionFatalWithExtra(): void
493 ResponseRenderer
::getInstance()->setAjax(false);
496 $extra = 'Appended Extra String';
498 $warn = 'The <a href="' . Core
::getPHPDocLink('book.' . $ext . '.php')
499 . '" target="Documentation"><em>' . $ext
500 . '</em></a> extension is missing. Please check your PHP configuration.'
503 $this->expectExceptionMessage($warn);
505 Core
::warnMissingExtension($ext, true, $extra);
509 * Test for Core::signSqlQuery
511 public function testSignSqlQuery(): void
513 $_SESSION[' HMAC_secret '] = hash('sha1', 'test');
514 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
515 $signature = Core
::signSqlQuery($sqlQuery);
516 $hmac = '33371e8680a640dc05944a2a24e6e630d3e9e3dba24464135f2fb954c3a4ffe2';
517 self
::assertSame($hmac, $signature, 'The signature must match the computed one');
521 * Test for Core::checkSqlQuerySignature
523 public function testCheckSqlQuerySignature(): void
525 $_SESSION[' HMAC_secret '] = hash('sha1', 'test');
526 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
527 $hmac = '33371e8680a640dc05944a2a24e6e630d3e9e3dba24464135f2fb954c3a4ffe2';
528 self
::assertTrue(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
532 * Test for Core::checkSqlQuerySignature
534 public function testCheckSqlQuerySignatureFails(): void
536 $_SESSION[' HMAC_secret '] = hash('sha1', '132654987gguieunofz');
537 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
538 $hmac = '33371e8680a640dc05944a2a24e6e630d3e9e3dba24464135f2fb954c3a4ffe2';
539 self
::assertFalse(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
543 * Test for Core::checkSqlQuerySignature
545 public function testCheckSqlQuerySignatureFailsBadHash(): void
547 $_SESSION[' HMAC_secret '] = hash('sha1', 'test');
548 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
549 $hmac = '3333333380a640dc05944a2a24e6e630d3e9e3dba24464135f2fb954c3eeeeee';
550 self
::assertFalse(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
554 * Test for Core::checkSqlQuerySignature
556 public function testCheckSqlQuerySignatureFailsNoSession(): void
558 $_SESSION[' HMAC_secret '] = 'empty';
559 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
560 $hmac = '3333333380a640dc05944a2a24e6e630d3e9e3dba24464135f2fb954c3eeeeee';
561 self
::assertFalse(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
565 * Test for Core::checkSqlQuerySignature
567 public function testCheckSqlQuerySignatureFailsFromAnotherSession(): void
569 $_SESSION[' HMAC_secret '] = hash('sha1', 'firstSession');
570 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
571 $hmac = Core
::signSqlQuery($sqlQuery);
572 self
::assertTrue(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
573 $_SESSION[' HMAC_secret '] = hash('sha1', 'secondSession');
574 // Try to use the token (hmac) from the previous session
575 self
::assertFalse(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
579 * Test for Core::checkSqlQuerySignature
581 public function testCheckSqlQuerySignatureFailsBlowfishSecretChanged(): void
583 $config = Config
::getInstance();
584 $config->settings
['blowfish_secret'] = '';
585 $_SESSION[' HMAC_secret '] = hash('sha1', 'firstSession');
586 $sqlQuery = 'SELECT * FROM `test`.`db` WHERE 1;';
587 $hmac = Core
::signSqlQuery($sqlQuery);
588 self
::assertTrue(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
589 $config->settings
['blowfish_secret'] = str_repeat('a', 32);
590 // Try to use the previous HMAC signature
591 self
::assertFalse(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
593 $config->settings
['blowfish_secret'] = str_repeat('a', 32);
594 // Generate the HMAC signature to check that it works
595 $hmac = Core
::signSqlQuery($sqlQuery);
596 // Must work now, (good secret and blowfish_secret)
597 self
::assertTrue(Core
::checkSqlQuerySignature($sqlQuery, $hmac));
600 public function testPopulateRequestWithEncryptedQueryParams(): void
603 $config = Config
::getInstance();
604 $config->set('URLQueryEncryption', true);
605 $config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
607 $_GET = ['pos' => '0', 'eq' => Url
::encryptQuery('{"db":"test_db","table":"test_table"}')];
610 $request = self
::createStub(ServerRequest
::class);
611 $request->method('getQueryParams')->willReturn($_GET);
612 $request->method('getParsedBody')->willReturn(null);
613 $request->method('withQueryParams')->willReturnSelf();
614 $request->method('withParsedBody')->willReturnSelf();
616 Core
::populateRequestWithEncryptedQueryParams($request);
618 $expected = ['pos' => '0', 'db' => 'test_db', 'table' => 'test_table'];
620 self
::assertSame($expected, $_GET);
621 self
::assertSame($expected, $_REQUEST);
625 * @param string[] $encrypted
626 * @param string[] $decrypted
628 #[DataProvider('providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam')]
629 public function testPopulateRequestWithEncryptedQueryParamsWithInvalidParam(
634 $config = Config
::getInstance();
635 $config->set('URLQueryEncryption', true);
636 $config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
639 $_REQUEST = $encrypted;
641 $request = self
::createStub(ServerRequest
::class);
642 $request->method('getQueryParams')->willReturn($_GET);
643 $request->method('getParsedBody')->willReturn(null);
644 $request->method('withQueryParams')->willReturnSelf();
645 $request->method('withParsedBody')->willReturnSelf();
647 Core
::populateRequestWithEncryptedQueryParams($request);
649 self
::assertSame($decrypted, $_GET);
650 self
::assertSame($decrypted, $_REQUEST);
653 /** @return array<int, array<int, array<string, string|mixed[]>>> */
654 public static function providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam(): array
656 return [[[], []], [['eq' => []], []], [['eq' => ''], []], [['eq' => 'invalid'], []]];
659 public function testGetDownloadHeaders(): void
661 $headersList = Core
::getDownloadHeaders('test.sql', 'text/x-sql', 100, false);
664 'Content-Description' => 'File Transfer',
665 'Content-Disposition' => 'attachment; filename="test.sql"',
666 'Content-Type' => 'text/x-sql',
667 'Content-Transfer-Encoding' => 'binary',
668 'Content-Length' => '100',
670 self
::assertSame($expected, $headersList);
673 public function testGetDownloadHeaders2(): void
675 $headersList = Core
::getDownloadHeaders('test.sql.gz', 'application/x-gzip', 0, false);
678 'Content-Description' => 'File Transfer',
679 'Content-Disposition' => 'attachment; filename="test.sql.gz"',
680 'Content-Type' => 'application/x-gzip',
681 'Content-Transfer-Encoding' => 'binary',
683 self
::assertSame($expected, $headersList);
686 public function testGetEnv(): void
688 self
::assertSame('', Core
::getEnv('PHPMYADMIN_GET_ENV_TEST'));
690 $_SERVER['PHPMYADMIN_GET_ENV_TEST'] = 'value_from_server_global';
691 $_ENV['PHPMYADMIN_GET_ENV_TEST'] = 'value_from_env_global';
692 putenv('PHPMYADMIN_GET_ENV_TEST=value_from_getenv');
694 self
::assertSame('value_from_server_global', Core
::getEnv('PHPMYADMIN_GET_ENV_TEST'));
695 unset($_SERVER['PHPMYADMIN_GET_ENV_TEST']);
697 self
::assertSame('value_from_env_global', Core
::getEnv('PHPMYADMIN_GET_ENV_TEST'));
698 unset($_ENV['PHPMYADMIN_GET_ENV_TEST']);
700 self
::assertSame('value_from_getenv', Core
::getEnv('PHPMYADMIN_GET_ENV_TEST'));
701 putenv('PHPMYADMIN_GET_ENV_TEST');
703 self
::assertSame('', Core
::getEnv('PHPMYADMIN_GET_ENV_TEST'));