From 38b5ee7314b798411cf8c99ce2a061f8863e27f9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 18 Sep 2021 01:02:26 +0200 Subject: [PATCH] WebHost: working web-gen --- Utils.py | 23 +++++--- WebHostLib/generate.py | 57 ++++++++----------- WebHostLib/upload.py | 125 ++++++++++++++++++++++------------------- 3 files changed, 106 insertions(+), 99 deletions(-) diff --git a/Utils.py b/Utils.py index d853513b..85fb8638 100644 --- a/Utils.py +++ b/Utils.py @@ -24,7 +24,7 @@ import pickle import functools import io import collections - +import importlib from yaml import load, dump, safe_load try: @@ -365,16 +365,25 @@ safe_builtins = { class RestrictedUnpickler(pickle.Unpickler): + def __init__(self, *args, **kwargs): + super(RestrictedUnpickler, self).__init__(*args, **kwargs) + self.options_module = importlib.import_module("Options") + self.net_utils_module = importlib.import_module("NetUtils") + def find_class(self, module, name): if module == "builtins" and name in safe_builtins: return getattr(builtins, name) + # used by MultiServer -> savegame/multidata if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint"}: - import NetUtils - return getattr(NetUtils, name) - if module == "Options": - import Options - obj = getattr(Options, name) - if issubclass(obj, Options.Option): + return getattr(self.net_utils_module, name) + # Options are unpickled by WebHost -> Generate + if module.endswith("Options"): + if module == "Options": + mod = self.options_module + else: + mod = importlib.import_module(module) + obj = getattr(mod, name) + if issubclass(obj, self.options_module.Option): return obj # Forbid everything else. raise pickle.UnpicklingError("global '%s.%s' is forbidden" % diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index bfb711d0..23b013cc 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -2,6 +2,7 @@ import os import tempfile import random import json +import zipfile from collections import Counter from flask import request, flash, redirect, url_for, session, render_template @@ -15,6 +16,7 @@ import pickle from .models import * from WebHostLib import app from .check import get_yaml_data, roll_options +from .upload import upload_zip_to_db @app.route('/generate', methods=['GET', 'POST']) @@ -69,7 +71,7 @@ def gen_game(gen_options, race=False, owner=None, sid=None): if race: random.seed() # reset to time-based random source - seedname = "M" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)) + seedname = "W" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)) erargs = parse_arguments(['--multi', str(playercount)]) erargs.seed = seed @@ -84,7 +86,10 @@ def gen_game(gen_options, race=False, owner=None, sid=None): for player, (playerfile, settings) in enumerate(gen_options.items(), 1): for k, v in settings.items(): if v is not None: - getattr(erargs, k)[player] = v + if hasattr(erargs, k): + getattr(erargs, k)[player] = v + else: + setattr(erargs, k, {player: v}) if not erargs.name[player]: erargs.name[player] = os.path.splitext(os.path.split(playerfile)[-1])[0] @@ -92,7 +97,7 @@ def gen_game(gen_options, race=False, owner=None, sid=None): ERmain(erargs, seed) - return upload_to_db(target.name, owner, sid, race) + return upload_to_db(target.name, sid, owner, race) except BaseException as e: if sid: with db_session: @@ -122,37 +127,19 @@ def wait_seed(seed: UUID): return render_template("waitSeed.html", seed_id=seed_id) -def upload_to_db(folder, owner, sid, race:bool): - slots = set() - spoiler = "" - - multidata = None +def upload_to_db(folder, sid, owner, race): for file in os.listdir(folder): file = os.path.join(folder, file) - if file.endswith(".apbp"): - player_text = file.split("_P", 1)[1] - player_name = player_text.split("_", 1)[1].split(".", 1)[0] - player_id = int(player_text.split(".", 1)[0].split("_", 1)[0]) - slots.add(Slot(data=open(file, "rb").read(), - player_id=player_id, player_name = player_name, game = "A Link to the Past")) - elif file.endswith(".txt"): - spoiler = open(file, "rt", encoding="utf-8-sig").read() - elif file.endswith(".archipelago"): - multidata = open(file, "rb").read() - if multidata: - with db_session: - if sid: - seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=owner, - id=sid, meta=json.dumps({"race": race, "tags": ["generated"]})) - else: - seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=owner, - meta=json.dumps({"race": race, "tags": ["generated"]})) - for patch in slots: - patch.seed = seed - if sid: - gen = Generation.get(id=sid) - if gen is not None: - gen.delete() - return seed.id - else: - raise Exception("Multidata required (.archipelago), but not found.") + if file.endswith(".zip"): + with db_session: + with zipfile.ZipFile(file) as zfile: + res = upload_zip_to_db(zfile, owner, {"race": race}, sid) + if type(res) == "str": + raise Exception(res) + elif res: + seed = res + gen = Generation.get(id=seed.id) + if gen is not None: + gen.delete() + return seed.id + raise Exception("Generation zipfile not found.") diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 8a010e3d..8a4c1ce3 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -3,6 +3,7 @@ import lzma import json import base64 import MultiServer +import uuid from flask import request, flash, redirect, url_for, session, render_template from pony.orm import flush, select @@ -17,6 +18,68 @@ accepted_zip_contents = {"patches": ".apbp", banned_zip_contents = (".sfc",) +def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, sid=None): + if not owner: + owner = session["_id"] + infolist = zfile.infolist() + slots = set() + spoiler = "" + multidata = None + for file in infolist: + if file.filename.endswith(banned_zip_contents): + return "Uploaded data contained a rom file, which is likely to contain copyrighted material. " \ + "Your file was deleted." + elif file.filename.endswith(".apbp"): + data = zfile.open(file, "r").read() + yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig")) + if yaml_data["version"] < 2: + return "Old format cannot be uploaded (outdated .apbp)", 500 + metadata = yaml_data["meta"] + slots.add(Slot(data=data, player_name=metadata["player_name"], + player_id=metadata["player_id"], + game="A Link to the Past")) + + elif file.filename.endswith(".apmc"): + data = zfile.open(file, "r").read() + metadata = json.loads(base64.b64decode(data).decode("utf-8")) + slots.add(Slot(data=data, player_name=metadata["player_name"], + player_id=metadata["player_id"], + game="Minecraft")) + + elif file.filename.endswith(".zip"): + # Factorio mods need a specific name or they do not function + _, seed_name, slot_id, slot_name = file.filename.rsplit("_", 1)[0].split("-") + slots.add(Slot(data=zfile.open(file, "r").read(), player_name=slot_name, + player_id=int(slot_id[1:]), game="Factorio")) + + elif file.filename.endswith(".apz5"): + # .apz5 must be named specifically since they don't contain any metadata + _, seed_name, slot_id, slot_name = file.filename.split('.')[0].split('_', 3) + slots.add(Slot(data=zfile.open(file, "r").read(), player_name=slot_name, + player_id=int(slot_id[1:]), game="Ocarina of Time")) + + elif file.filename.endswith(".txt"): + spoiler = zfile.open(file, "r").read().decode("utf-8-sig") + elif file.filename.endswith(".archipelago"): + try: + multidata = zfile.open(file).read() + MultiServer.Context._decompress(multidata) + except: + flash("Could not load multidata. File may be corrupted or incompatible.") + else: + multidata = zfile.open(file).read() + if multidata: + flush() # commit slots + seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=owner, meta=json.dumps(meta), + id=sid if sid else uuid.uuid4()) + flush() # create seed + for slot in slots: + slot.seed = seed + return seed + else: + flash("No multidata was found in the zip file, which is required.") + + @app.route('/uploads', methods=['GET', 'POST']) def uploads(): if request.method == 'POST': @@ -31,64 +94,12 @@ def uploads(): flash('No selected file') elif file and allowed_file(file.filename): if file.filename.endswith(".zip"): - slots = set() - spoiler = "" - multidata = None with zipfile.ZipFile(file, 'r') as zfile: - infolist = zfile.infolist() - - for file in infolist: - if file.filename.endswith(banned_zip_contents): - return "Uploaded data contained a rom file, which is likely to contain copyrighted material. Your file was deleted." - elif file.filename.endswith(".apbp"): - data = zfile.open(file, "r").read() - yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig")) - if yaml_data["version"] < 2: - return "Old format cannot be uploaded (outdated .apbp)", 500 - metadata = yaml_data["meta"] - slots.add(Slot(data=data, player_name=metadata["player_name"], - player_id=metadata["player_id"], - game="A Link to the Past")) - - elif file.filename.endswith(".apmc"): - data = zfile.open(file, "r").read() - metadata = json.loads(base64.b64decode(data).decode("utf-8")) - slots.add(Slot(data=data, player_name=metadata["player_name"], - player_id=metadata["player_id"], - game="Minecraft")) - - elif file.filename.endswith(".zip"): - # Factorio mods needs a specific name or they do not function - _, seed_name, slot_id, slot_name = file.filename.rsplit("_", 1)[0].split("-") - slots.add(Slot(data=zfile.open(file, "r").read(), player_name=slot_name, - player_id=int(slot_id[1:]), game="Factorio")) - - elif file.filename.endswith(".apz5"): - # .apz5 must be named specifically since they don't contain any metadata - _, seed_name, slot_id, slot_name = file.filename.split('.')[0].split('_', 3) - slots.add(Slot(data=zfile.open(file, "r").read(), player_name=slot_name, - player_id=int(slot_id[1:]), game="Ocarina of Time")) - - elif file.filename.endswith(".txt"): - spoiler = zfile.open(file, "r").read().decode("utf-8-sig") - elif file.filename.endswith(".archipelago"): - try: - multidata = zfile.open(file).read() - MultiServer.Context._decompress(multidata) - except: - flash("Could not load multidata. File may be corrupted or incompatible.") - else: - multidata = zfile.open(file).read() - if multidata: - flush() # commit slots - seed = Seed(multidata=multidata, spoiler=spoiler, slots=slots, owner=session["_id"]) - flush() # create seed - for slot in slots: - slot.seed = seed - - return redirect(url_for("viewSeed", seed=seed.id)) - else: - flash("No multidata was found in the zip file, which is required.") + res = upload_zip_to_db(zfile) + if type(res) == str: + return res + elif res: + return redirect(url_for("viewSeed", seed=res.id)) else: try: multidata = file.read()