Merge pull request #19552 from kamil-tekiela/Fix-default-values
[phpmyadmin.git] / tests / unit / CoreTest.php
blob00e673dbe49129483d1b7629af996c63dc7d2c45
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin\Tests;
7 use PhpMyAdmin\Config;
8 use PhpMyAdmin\Core;
9 use PhpMyAdmin\Current;
10 use PhpMyAdmin\Dbal\DatabaseInterface;
11 use PhpMyAdmin\Http\ServerRequest;
12 use PhpMyAdmin\ResponseRenderer;
13 use PhpMyAdmin\Url;
14 use PHPUnit\Framework\Attributes\CoversClass;
15 use PHPUnit\Framework\Attributes\DataProvider;
16 use PHPUnit\Framework\Attributes\Group;
17 use stdClass;
19 use function _pgettext;
20 use function hash;
21 use function putenv;
22 use function serialize;
23 use function str_repeat;
25 #[CoversClass(Core::class)]
26 class CoreTest extends AbstractTestCase
28 /**
29 * Setup for test cases
31 protected function setUp(): void
33 parent::setUp();
35 $this->setLanguage();
37 DatabaseInterface::$instance = $this->createDatabaseInterface();
38 Current::$database = '';
39 Current::$table = '';
40 Config::getInstance()->set('URLQueryEncryption', false);
43 /**
44 * Test for Core::arrayRead
46 public function testArrayRead(): void
48 $arr = [
49 'int' => 1,
50 'str' => 'str_val',
51 'arr' => ['val1', 'val2', 'val3'],
52 'sarr' => ['arr1' => [1, 2, 3], [3, ['a', 'b', 'c'], 4]],
55 self::assertSame(
56 Core::arrayRead('int', $arr),
57 $arr['int'],
60 self::assertSame(
61 Core::arrayRead('str', $arr),
62 $arr['str'],
65 self::assertSame(
66 Core::arrayRead('arr/0', $arr),
67 $arr['arr'][0],
70 self::assertSame(
71 Core::arrayRead('arr/1', $arr),
72 $arr['arr'][1],
75 self::assertSame(
76 Core::arrayRead('arr/2', $arr),
77 $arr['arr'][2],
80 self::assertSame(
81 Core::arrayRead('sarr/arr1/0', $arr),
82 $arr['sarr']['arr1'][0],
85 self::assertSame(
86 Core::arrayRead('sarr/arr1/1', $arr),
87 $arr['sarr']['arr1'][1],
90 self::assertSame(
91 Core::arrayRead('sarr/arr1/2', $arr),
92 $arr['sarr']['arr1'][2],
95 self::assertSame(
96 Core::arrayRead('sarr/0/0', $arr),
97 $arr['sarr'][0][0],
100 self::assertSame(
101 Core::arrayRead('sarr/0/1', $arr),
102 $arr['sarr'][0][1],
105 self::assertSame(
106 Core::arrayRead('sarr/0/1/2', $arr),
107 $arr['sarr'][0][1][2],
110 self::assertNull(
111 Core::arrayRead('sarr/not_exiting/1', $arr),
114 self::assertSame(
115 Core::arrayRead('sarr/not_exiting/1', $arr, 0),
119 self::assertSame(
120 Core::arrayRead('sarr/not_exiting/1', $arr, 'default_val'),
121 'default_val',
126 * Test for Core::arrayWrite
128 public function testArrayWrite(): void
130 $arr = [
131 'int' => 1,
132 'str' => 'str_val',
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
179 $arr = [
180 'int' => 1,
181 'str' => 'str_val',
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);
201 $tmpArr = $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
250 return [
251 ['', false],
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
280 return [
281 ['0', 0],
282 ['1kb', 1024],
283 ['1024k', 1024 * 1024],
284 ['8m', 8 * 1024 * 1024],
285 ['12gb', 12 * 1024 * 1024 * 1024],
286 ['1024', 1024],
287 ['8000m', 8 * 1000 * 1024 * 1024],
288 ['8G', 8 * 1024 * 1024 * 1024],
289 ['2048', 2048],
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');
302 self::assertSame(
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
328 return [
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';
340 self::assertSame(
341 $expected,
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
352 return [
353 ['', false],
354 ['//', false],
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],
371 ['//php.net', 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
389 self::assertSame(
390 $expected,
391 Core::safeUnserialize($data),
396 * Test data provider
398 * @return array<array{string, mixed}>
400 public static function provideTestSafeUnserialize(): array
402 return [
403 ['s:6:"foobar";', 'foobar'],
404 ['foobar', null],
405 ['b:0;', false],
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:{}}',
411 null,
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
429 self::assertSame(
430 $expected,
431 Core::sanitizeMySQLHost($host),
436 * Test data provider
438 * @return array<array{string, string}>
440 public static function provideTestSanitizeMySQLHost(): array
442 return [
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
455 self::assertSame(
456 Core::securePath('../../../etc/passwd'),
457 './././etc/passwd',
459 self::assertSame(
460 Core::securePath('/var/www/../phpmyadmin'),
461 '/var/www/./phpmyadmin',
463 self::assertSame(
464 Core::securePath('./path/with..dots/../../file..php'),
465 './path/with.dots/././file.php',
470 * Test for Core::warnMissingExtension
472 public function testMissingExtensionFatal(): void
474 $_REQUEST = [];
475 ResponseRenderer::getInstance()->setAjax(false);
477 $ext = 'php_ext';
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
492 $_REQUEST = [];
493 ResponseRenderer::getInstance()->setAjax(false);
495 $ext = 'php_ext';
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.'
501 . ' ' . $extra;
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
602 $_SESSION = [];
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"}')];
608 $_REQUEST = $_GET;
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(
630 array $encrypted,
631 array $decrypted,
632 ): void {
633 $_SESSION = [];
634 $config = Config::getInstance();
635 $config->set('URLQueryEncryption', true);
636 $config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
638 $_GET = $encrypted;
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);
663 $expected = [
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);
677 $expected = [
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'));