fix: allow remote icon paths in generals
This commit is contained in:
+1
-1
@@ -11,7 +11,7 @@ CREATE TABLE `general` (
|
||||
`bornyear` INT(3) NULL DEFAULT '180',
|
||||
`deadyear` INT(3) NULL DEFAULT '300',
|
||||
`newmsg` INT(1) NULL DEFAULT '0',
|
||||
`picture` VARCHAR(40) NOT NULL,
|
||||
`picture` VARCHAR(64) NOT NULL,
|
||||
`imgsvr` INT(1) NOT NULL DEFAULT '0',
|
||||
`name` VARCHAR(32) NOT NULL COLLATE 'utf8mb4_bin',
|
||||
`owner_name` VARCHAR(32) NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Deny from all
|
||||
</IfModule>
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use sammo\DB;
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(404);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] ??= '127.0.0.1';
|
||||
$_SERVER['REQUEST_URI'] ??= '/cli/migrate-general-picture';
|
||||
|
||||
require dirname(__DIR__) . '/hwe/lib.php';
|
||||
require dirname(__DIR__) . '/hwe/func.php';
|
||||
|
||||
/** @return never */
|
||||
function pictureMigrationUsage(int $exitCode = 0): void
|
||||
{
|
||||
$stream = $exitCode === 0 ? STDOUT : STDERR;
|
||||
fwrite($stream, <<<'TEXT'
|
||||
Usage:
|
||||
php scripts/migrate-general-picture.php --status
|
||||
php scripts/migrate-general-picture.php --apply --backup=/absolute/path/to/pre-migration.sql
|
||||
|
||||
--status is read-only. --apply widens general.picture from VARCHAR(40) to
|
||||
VARCHAR(64), and requires a pre-existing, non-empty SQL backup. Stop web and
|
||||
daemon traffic before applying; MariaDB/Aria DDL is not transactional.
|
||||
|
||||
TEXT);
|
||||
exit($exitCode);
|
||||
}
|
||||
|
||||
function pictureColumnCapacity(\MeekroDB $db): ?int
|
||||
{
|
||||
$column = $db->queryFirstRow('SHOW COLUMNS FROM general WHERE Field = %s', 'picture');
|
||||
if (!$column || !is_string($column['Type'] ?? null)) {
|
||||
return null;
|
||||
}
|
||||
if (preg_match('/^varchar\((\d+)\)$/i', $column['Type'], $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
return (int)$matches[1];
|
||||
}
|
||||
|
||||
function pictureMigrationState(\MeekroDB $db): string
|
||||
{
|
||||
$capacity = pictureColumnCapacity($db);
|
||||
if ($capacity === 40) {
|
||||
return 'legacy';
|
||||
}
|
||||
if ($capacity !== null && $capacity >= 64) {
|
||||
return 'ready';
|
||||
}
|
||||
return 'unsupported';
|
||||
}
|
||||
|
||||
function printPictureMigrationStatus(\MeekroDB $db): string
|
||||
{
|
||||
$state = pictureMigrationState($db);
|
||||
$capacity = pictureColumnCapacity($db);
|
||||
printf("schema_state=%s\npicture_capacity=%s\n", $state, $capacity ?? 'unknown');
|
||||
return $state;
|
||||
}
|
||||
|
||||
$options = getopt('', ['help', 'status', 'apply', 'backup:']);
|
||||
if (isset($options['help'])) {
|
||||
pictureMigrationUsage();
|
||||
}
|
||||
if (isset($options['status']) === isset($options['apply'])) {
|
||||
pictureMigrationUsage(2);
|
||||
}
|
||||
|
||||
$db = DB::db();
|
||||
if (isset($options['status'])) {
|
||||
exit(printPictureMigrationStatus($db) === 'unsupported' ? 2 : 0);
|
||||
}
|
||||
|
||||
$state = pictureMigrationState($db);
|
||||
if ($state === 'ready') {
|
||||
fwrite(STDOUT, "general.picture is already VARCHAR(64) or wider; nothing to do.\n");
|
||||
exit(0);
|
||||
}
|
||||
if ($state !== 'legacy') {
|
||||
fwrite(STDERR, "general.picture is not the supported VARCHAR(40) schema; inspect --status first.\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$backup = $options['backup'] ?? null;
|
||||
if (!is_string($backup) || $backup === '' || $backup[0] !== '/' || !is_file($backup) || filesize($backup) === 0) {
|
||||
fwrite(STDERR, "--backup must name a pre-existing, non-empty absolute SQL backup made immediately before migration.\n");
|
||||
exit(2);
|
||||
}
|
||||
if (!\sammo\tryLock()) {
|
||||
fwrite(STDERR, "Unable to acquire the GAME lock.\n");
|
||||
exit(3);
|
||||
}
|
||||
|
||||
try {
|
||||
$db->query('ALTER TABLE general MODIFY picture VARCHAR(64) NOT NULL');
|
||||
} finally {
|
||||
\sammo\unlock();
|
||||
}
|
||||
|
||||
if (printPictureMigrationStatus($db) !== 'ready') {
|
||||
fwrite(STDERR, "general.picture migration verification failed; restore the supplied backup.\n");
|
||||
exit(4);
|
||||
}
|
||||
fwrite(STDOUT, "general.picture migration completed.\n");
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace sammo;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class GeneralPictureSchemaTest extends TestCase
|
||||
{
|
||||
public function testGameAndAccountSchemasAcceptRemoteUserIconPaths(): void
|
||||
{
|
||||
$gameSchema = file_get_contents(__DIR__ . '/../hwe/sql/schema.sql');
|
||||
$accountSchema = file_get_contents(__DIR__ . '/../f_install/sql/common_schema.sql');
|
||||
self::assertIsString($gameSchema);
|
||||
self::assertIsString($accountSchema);
|
||||
self::assertMatchesRegularExpression('/`picture`\s+VARCHAR\(64\)\s+NOT NULL/i', $gameSchema);
|
||||
self::assertMatchesRegularExpression('/`PICTURE`\s+VARCHAR\(64\)/i', $accountSchema);
|
||||
|
||||
$longestRemotePath = 'users/core/' . str_repeat('a', 32) . '.jpeg?=20260807';
|
||||
self::assertGreaterThan(40, strlen($longestRemotePath));
|
||||
self::assertLessThanOrEqual(64, strlen($longestRemotePath));
|
||||
}
|
||||
|
||||
public function testExistingGameMigrationWidensOnlyThePictureColumn(): void
|
||||
{
|
||||
$migration = file_get_contents(__DIR__ . '/../scripts/migrate-general-picture.php');
|
||||
self::assertIsString($migration);
|
||||
self::assertStringContainsString(
|
||||
'ALTER TABLE general MODIFY picture VARCHAR(64) NOT NULL',
|
||||
$migration,
|
||||
);
|
||||
self::assertStringNotContainsString('UPDATE general', $migration);
|
||||
self::assertStringContainsString("if (\$state === 'ready')", $migration);
|
||||
}
|
||||
|
||||
public function testScriptsDirectoryIsDeniedOverApache(): void
|
||||
{
|
||||
$accessRules = file_get_contents(__DIR__ . '/../scripts/.htaccess');
|
||||
self::assertIsString($accessRules);
|
||||
self::assertStringContainsString('Require all denied', $accessRules);
|
||||
self::assertStringContainsString('Deny from all', $accessRules);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user