Skip to content

WebHost: validate uploaded datapackage and calculate own checksum #2639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 28, 2023
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions WebHostLib/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,30 @@
from markupsafe import Markup
from pony.orm import commit, flush, select, rollback
from pony.orm.core import TransactionIntegrityError
import schema

import MultiServer
from NetUtils import SlotType
from Utils import VersionException, __version__
from worlds import GamesPackage
from worlds.Files import AutoPatchRegister
from worlds.AutoWorld import data_package_checksum
from . import app
from .models import Seed, Room, Slot, GameDataPackage

banned_extensions = (".sfc", ".z64", ".n64", ".nes", ".smc", ".sms", ".gb", ".gbc", ".gba")
allowed_options_extensions = (".yaml", ".json", ".yml", ".txt", ".zip")
allowed_generation_extensions = (".archipelago", ".zip")

games_package_schema = schema.Schema({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • did you look into validating via TypedDict instead of Schema? (we have a TypedDict in worlds/__init__.py. I read up on that and the frameworks that support(ed) that were massive, so I gave up on that idea, but I may have missed something)
  • should we maybe move the Schema to worlds/__init__.py so it's local to the TypedDict?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that we already have a typed dict, but also am not a aware of a small and quick way of validating it. But we do already have schema, so I went with that.

Second, maybe, I considered that too, I ended up moving it to webhost to keep init time low as we avoid a schema import if we don't need it. But I also didn't measure how long the module needs, so this may be over-engineered.

"item_name_groups": {str: [str]},
"item_name_to_id": {str: int},
"location_name_groups": {str: [str]},
"location_name_to_id": {str: int},
schema.Optional("checksum"): str,
schema.Optional("version"): int,
})


def allowed_options(filename: str) -> bool:
return filename.endswith(allowed_options_extensions)
Expand All @@ -37,6 +49,8 @@ def banned_file(filename: str) -> bool:


def process_multidata(compressed_multidata, files={}):
game_data: GamesPackage

decompressed_multidata = MultiServer.Context.decompress(compressed_multidata)

slots: typing.Set[Slot] = set()
Expand All @@ -45,11 +59,20 @@ def process_multidata(compressed_multidata, files={}):
game_data_packages: typing.List[GameDataPackage] = []
for game, game_data in decompressed_multidata["datapackage"].items():
if game_data.get("checksum"):
original_checksum = game_data.pop("checksum")
game_data = games_package_schema.validate(game_data)
game_data = {key: value for key, value in sorted(game_data.items())}
game_data["checksum"] = data_package_checksum(game_data)
game_data_package = GameDataPackage(checksum=game_data["checksum"],
data=pickle.dumps(game_data))
if original_checksum != game_data["checksum"]:
import logging
logging.warning(f"Original checksum {original_checksum} != "
f"calculated checksum {game_data['checksum']} "
f"for game {game}")
decompressed_multidata["datapackage"][game] = {
"version": game_data.get("version", 0),
"checksum": game_data["checksum"]
"checksum": game_data["checksum"],
}
try:
commit() # commit game data package
Expand All @@ -64,14 +87,15 @@ def process_multidata(compressed_multidata, files={}):
if slot_info.type == SlotType.group:
continue
slots.add(Slot(data=files.get(slot, None),
player_name=slot_info.name,
player_id=slot,
game=slot_info.game))
player_name=slot_info.name,
player_id=slot,
game=slot_info.game))
flush() # commit slots

compressed_multidata = compressed_multidata[0:1] + zlib.compress(pickle.dumps(decompressed_multidata), 9)
return slots, compressed_multidata


def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, sid=None):
if not owner:
owner = session["_id"]
Expand Down