files as $path) { unlink($path); } parent::tearDown(); } public function test_reads_mysqldump_column_order_and_escaped_binary_without_hex_blob(): void { $bytes = "\0\n\r\t\x08\x1a'\"\\".substr(hash('sha256', 'fixture', true), 0, 23); $literal = strtr($bytes, [ "\0" => '\\0', "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', "\x08" => '\\b', "\x1a" => '\\Z', "'" => "\\'", '"' => '\\"', '\\' => '\\\\', ]); $sql = <<rows($this->file($sql))); $this->assertCount(3, $rows); $this->assertSame('account', $rows[0]['table']); $this->assertSame(['id' => '42', 'username' => 'SINDORING', 'salt' => $bytes, 'verifier' => $bytes, 'email' => null], $rows[0]['row']); $this->assertSame("\0", $rows[1]['row']['salt']); $this->assertSame("\xff", $rows[1]['row']['verifier']); $this->assertSame("quoted'mail", $rows[1]['row']['email']); $this->assertSame(['table' => 'account_access', 'row' => ['id' => '42', 'gmlevel' => '3', 'RealmID' => '-1']], $rows[2]); } public function test_gzip_filters_source_database_and_ignores_unrelated_sql(): void { $sql = <<<'SQL' USE `other_auth`; INSERT INTO `account` (`id`,`username`) VALUES (1,'OTHER'); DROP DATABASE `moonwell-web`; USE `acore_auth`; INSERT IGNORE INTO `account` (`id`,`username`,`email`) VALUES (42,_utf8mb4 'SINDORING','semicolon;value'); INSERT INTO `other_auth`.`account_access` (`id`,`gmlevel`) VALUES (1,3); SQL; $rows = iterator_to_array((new AzerothCoreAccountDumpReader)->rows($this->file(gzencode($sql)))); $this->assertSame([['table' => 'account', 'row' => ['id' => '42', 'username' => 'SINDORING', 'email' => 'semicolon;value']]], $rows); } #[DataProvider('invalidStatements')] public function test_rejects_unsupported_or_incomplete_account_sql(string $sql): void { $this->expectException(RuntimeException::class); iterator_to_array((new AzerothCoreAccountDumpReader)->rows($this->file($sql))); } public static function invalidStatements(): array { return [ 'missing column definitions' => ["INSERT INTO `account` VALUES (1,'PLAYER');"], 'column count mismatch' => ["INSERT INTO `account` (`id`) VALUES (1,'PLAYER');"], 'truncated' => ['INSERT INTO `account` (`id`) VALUES (1'], 'expressions' => ["INSERT INTO `account` (`salt`) VALUES (UNHEX('abcd'));"], 'appended SQL' => ['INSERT INTO `account` (`id`) VALUES (1); DROP DATABASE `web`;'], ]; } private function file(string $contents): string { $path = tempnam(sys_get_temp_dir(), 'auth-parser-'); file_put_contents($path, $contents); $this->files[] = $path; return $path; } }