MinetestCheck: Validate supported_games syntax

This commit is contained in:
rubenwardy 2024-01-10 00:07:31 +00:00
parent 0ffc402d67
commit 69ba1c3fad
3 changed files with 34 additions and 21 deletions

@ -32,7 +32,8 @@ from app.models import AuditSeverity, db, NotificationType, PackageRelease, Meta
MinetestRelease, Package, PackageState, PackageScreenshot, PackageUpdateTrigger, PackageUpdateConfig, \ MinetestRelease, Package, PackageState, PackageScreenshot, PackageUpdateTrigger, PackageUpdateConfig, \
PackageGameSupport PackageGameSupport
from app.tasks import celery, TaskError from app.tasks import celery, TaskError
from app.utils import random_string, post_bot_message, add_system_notification, add_system_audit_log, get_games_from_csv from app.utils import random_string, post_bot_message, add_system_notification, add_system_audit_log, \
get_games_from_list
from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_temp_dir from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_temp_dir
from .minetestcheck import build_tree, MinetestCheckError, ContentType from .minetestcheck import build_tree, MinetestCheckError, ContentType
from app import app from app import app
@ -163,7 +164,7 @@ def post_release_check_update(self, release: PackageRelease, path):
game_is_supported = {} game_is_supported = {}
if "supported_games" in tree.meta: if "supported_games" in tree.meta:
for game in get_games_from_csv(db.session, tree.meta["supported_games"]): for game in get_games_from_list(db.session, tree.meta["supported_games"]):
game_is_supported[game.id] = True game_is_supported[game.id] = True
has_star = any(map(lambda x: x.strip() == "*", tree.meta["supported_games"].split(","))) has_star = any(map(lambda x: x.strip() == "*", tree.meta["supported_games"].split(",")))
@ -175,7 +176,7 @@ def post_release_check_update(self, release: PackageRelease, path):
package.supports_all_games = True package.supports_all_games = True
if "unsupported_games" in tree.meta: if "unsupported_games" in tree.meta:
for game in get_games_from_csv(db.session, tree.meta["unsupported_games"]): for game in get_games_from_list(db.session, tree.meta["unsupported_games"]):
game_is_supported[game.id] = False game_is_supported[game.id] = False
resolver.set_supported(package, game_is_supported, 10) resolver.set_supported(package, game_is_supported, 10)

@ -23,6 +23,7 @@ from . import MinetestCheckError, ContentType
from .config import parse_conf from .config import parse_conf
basenamePattern = re.compile("^([a-z0-9_]+)$") basenamePattern = re.compile("^([a-z0-9_]+)$")
licensePattern = re.compile("^(licen[sc]e|copying)(.[^/\n]+)?$", re.IGNORECASE)
DISALLOWED_NAMES = { DISALLOWED_NAMES = {
"core", "minetest", "group", "table", "string", "lua", "luajit", "assert", "debug", "core", "minetest", "group", "table", "string", "lua", "luajit", "assert", "debug",
@ -64,6 +65,19 @@ def get_csv_line(line):
return [x.strip() for x in line.split(",") if x.strip() != ""] return [x.strip() for x in line.split(",") if x.strip() != ""]
def check_name_list(key: str, value: list[str], relative: str, allow_star: bool = False):
for dep in value:
if not basenamePattern.match(dep):
if dep == "*" and allow_star:
continue
elif " " in dep:
raise MinetestCheckError(
f"Invalid {key} name '{dep}' at {relative}, did you forget a comma?")
else:
raise MinetestCheckError(
f"Invalid {key} name '{dep}' at {relative}, names must only contain a-z0-9_.")
class PackageTreeNode: class PackageTreeNode:
def __init__(self, base_dir, relative, author=None, repo=None, name=None): def __init__(self, base_dir, relative, author=None, repo=None, name=None):
self.baseDir = base_dir self.baseDir = base_dir
@ -94,10 +108,9 @@ class PackageTreeNode:
self.add_children_from_mod_dir(None) self.add_children_from_mod_dir(None)
def find_license_file(self) -> Optional[str]: def find_license_file(self) -> Optional[str]:
names = ["LICENSE", "LICENSE.md", "LICENSE.txt", "COPYING"] for name in os.listdir(self.baseDir):
for name in names:
path = os.path.join(self.baseDir, name) path = os.path.join(self.baseDir, name)
if os.path.isfile(path): if os.path.isfile(path) and licensePattern.match(name):
return path return path
return None return None
@ -182,20 +195,17 @@ class PackageTreeNode:
result["depends"] = [] result["depends"] = []
result["optional_depends"] = [] result["optional_depends"] = []
def check_dependencies(deps): # Read supported games
for dep in deps: result["supported_games"] = get_csv_line(result.get("supported_games", ""))
if not basenamePattern.match(dep): result["unsupported_games"] = get_csv_line(result.get("unsupported_games", ""))
if " " in dep:
raise MinetestCheckError("Invalid dependency name '{}' for mod at {}, did you forget a comma?" \
.format(dep, self.relative))
else:
raise MinetestCheckError(
"Invalid dependency name '{}' for mod at {}, names must only contain a-z0-9_." \
.format(dep, self.relative))
# Check dependencies # Check dependencies
check_dependencies(result["depends"]) check_name_list("depends", result["depends"], self.relative)
check_dependencies(result["optional_depends"]) check_name_list("optional_depends", result["optional_depends"], self.relative)
# Check supported games
check_name_list("supported_games", result["supported_games"], self.relative, True)
check_name_list("unsupported_games", result["unsupported_games"], self.relative)
# Fix games using "name" as "title" # Fix games using "name" as "title"
if self.type == ContentType.GAME and "name" in result: if self.type == ContentType.GAME and "name" in result:
@ -203,7 +213,7 @@ class PackageTreeNode:
del result["name"] del result["name"]
# Calculate Title # Calculate Title
if "name" in result and not "title" in result: if "name" in result and "title" not in result:
result["title"] = result["name"].replace("_", " ").title() result["title"] = result["name"].replace("_", " ").title()
# Calculate short description # Calculate short description

@ -140,10 +140,12 @@ def post_bot_message(package: Package, title: str, message: str):
def get_games_from_csv(session: sqlalchemy.orm.Session, csv: str) -> List[Package]: def get_games_from_csv(session: sqlalchemy.orm.Session, csv: str) -> List[Package]:
return get_games_from_list(session, [name.strip() for name in csv.split(",")])
def get_games_from_list(session: sqlalchemy.orm.Session, supported_games_raw: list[str]) -> List[Package]:
retval = [] retval = []
supported_games_raw = csv.split(",")
for game_name in supported_games_raw: for game_name in supported_games_raw:
game_name = game_name.strip()
if game_name.endswith("_game"): if game_name.endswith("_game"):
game_name = game_name[:-5] game_name = game_name[:-5]
games = session.query(Package).filter(and_(Package.state==PackageState.APPROVED, Package.type==PackageType.GAME, games = session.query(Package).filter(and_(Package.state==PackageState.APPROVED, Package.type==PackageType.GAME,