diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py
index 4005e965..27d68141 100644
--- a/app/classes/shared/file_helpers.py
+++ b/app/classes/shared/file_helpers.py
@@ -325,3 +325,12 @@ class FileHelpers:
else:
return "false"
return
+
+ def unzip_server(self, zip_path, user_id):
+ if Helpers.check_file_perms(zip_path):
+ temp_dir = tempfile.mkdtemp()
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
+ # extracts archive to temp directory
+ zip_ref.extractall(temp_dir)
+ if user_id:
+ return temp_dir
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index 9acf1187..576dc654 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -1135,17 +1135,6 @@ class Helpers:
"""
return output
- def unzip_server(self, zip_path, user_id):
- if Helpers.check_file_perms(zip_path):
- temp_dir = tempfile.mkdtemp()
- with zipfile.ZipFile(zip_path, "r") as zip_ref:
- # extracts archive to temp directory
- zip_ref.extractall(temp_dir)
- if user_id:
- self.websocket_helper.broadcast_user(
- user_id, "send_temp_path", {"path": temp_dir}
- )
-
@staticmethod
def unzip_backup_archive(backup_path, zip_name):
zip_path = os.path.join(backup_path, zip_name)
diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py
index acdc1cac..cc2a8671 100644
--- a/app/classes/shared/tasks.py
+++ b/app/classes/shared/tasks.py
@@ -41,10 +41,10 @@ scheduler_intervals = {
class TasksManager:
controller: Controller
- def __init__(self, helper, controller):
+ def __init__(self, helper, controller, file_helper):
self.helper: Helpers = helper
self.controller: Controller = controller
- self.tornado: Webserver = Webserver(helper, controller, self)
+ self.tornado: Webserver = Webserver(helper, controller, self, file_helper)
try:
self.tz = get_localzone()
except ZoneInfoNotFoundError as e:
diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py
index e38648f4..0fa17dcf 100644
--- a/app/classes/web/ajax_handler.py
+++ b/app/classes/web/ajax_handler.py
@@ -9,7 +9,7 @@ import bleach
import tornado.web
import tornado.escape
-from app.classes.shared.console import Console
+from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.helpers import Helpers
from app.classes.shared.server import ServerOutBuf
from app.classes.web.base_handler import BaseHandler
@@ -148,32 +148,6 @@ class AjaxHandler(BaseHandler):
self.controller.cached_login = "login_1.jpg"
return
- elif page == "unzip_server":
- path = urllib.parse.unquote(self.get_argument("path", ""))
- if not path:
- path = os.path.join(
- self.controller.project_root,
- "imports",
- urllib.parse.unquote(self.get_argument("file", "")),
- )
- if Helpers.check_file_exists(path):
- self.helper.unzip_server(path, exec_user["user_id"])
- else:
- user_id = exec_user["user_id"]
- if user_id:
- time.sleep(5)
- user_lang = self.controller.users.get_user_lang_by_id(user_id)
- self.helper.websocket_helper.broadcast_user(
- user_id,
- "send_start_error",
- {
- "error": self.helper.translation.translate(
- "error", "no-file", user_lang
- )
- },
- )
- return
-
elif page == "jar_cache":
if not superuser:
self.redirect("/panel/error?error=Not a super user")
@@ -208,25 +182,3 @@ class AjaxHandler(BaseHandler):
new_dir = urllib.parse.unquote(self.get_argument("server_dir"))
self.controller.update_master_server_dir(new_dir, exec_user["user_id"])
return
-
- def check_server_id(self, server_id, page_name):
- if server_id is None:
- logger.warning(
- f"Server ID not defined in {page_name} ajax call ({server_id})"
- )
- Console.warning(
- f"Server ID not defined in {page_name} ajax call ({server_id})"
- )
- return
- server_id = bleach.clean(server_id)
-
- # does this server id exist?
- if not self.controller.servers.server_id_exists(server_id):
- logger.warning(
- f"Server ID not found in {page_name} ajax call ({server_id})"
- )
- Console.warning(
- f"Server ID not found in {page_name} ajax call ({server_id})"
- )
- return
- return True
diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py
index e772d633..33fe9936 100644
--- a/app/classes/web/base_handler.py
+++ b/app/classes/web/base_handler.py
@@ -8,6 +8,7 @@ import tornado.web
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.users import ApiKeys
from app.classes.shared.helpers import Helpers
+from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.main_controller import Controller
from app.classes.shared.translation import Translation
from app.classes.models.management import DatabaseShortcuts
@@ -24,15 +25,22 @@ class BaseHandler(tornado.web.RequestHandler):
helper: Helpers
controller: Controller
translator: Translation
+ file_helper: FileHelpers
# noinspection PyAttributeOutsideInit
def initialize(
- self, helper=None, controller=None, tasks_manager=None, translator=None
+ self,
+ helper=None,
+ controller=None,
+ tasks_manager=None,
+ translator=None,
+ file_helper=None,
):
self.helper = helper
self.controller = controller
self.tasks_manager = tasks_manager
self.translator = translator
+ self.file_helper = file_helper
def set_default_headers(self) -> None:
"""
diff --git a/app/classes/web/routes/api/api_handlers.py b/app/classes/web/routes/api/api_handlers.py
index 43d21d08..102161ab 100644
--- a/app/classes/web/routes/api/api_handlers.py
+++ b/app/classes/web/routes/api/api_handlers.py
@@ -51,6 +51,7 @@ from app.classes.web.routes.api.users.user.pfp import ApiUsersUserPfpHandler
from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandler
from app.classes.web.routes.api.crafty.config.index import ApiCraftyConfigIndexHandler
from app.classes.web.routes.api.crafty.clogs.index import ApiCraftyLogIndexHandler
+from app.classes.web.routes.api.crafty.imports.index import ApiImportFilesIndexHandler
def api_handlers(handler_args):
@@ -76,6 +77,11 @@ def api_handlers(handler_args):
ApiCraftyLogIndexHandler,
handler_args,
),
+ (
+ r"/api/v2/import/file/unzip/?",
+ ApiImportFilesIndexHandler,
+ handler_args,
+ ),
# User routes
(
r"/api/v2/users/?",
diff --git a/app/classes/web/routes/api/crafty/imports/index.py b/app/classes/web/routes/api/crafty/imports/index.py
new file mode 100644
index 00000000..3bcae9af
--- /dev/null
+++ b/app/classes/web/routes/api/crafty/imports/index.py
@@ -0,0 +1,123 @@
+import os
+import logging
+import json
+import html
+from jsonschema import validate
+from jsonschema.exceptions import ValidationError
+from app.classes.models.crafty_permissions import EnumPermissionsCrafty
+from app.classes.shared.helpers import Helpers
+from app.classes.web.base_api_handler import BaseApiHandler
+
+logger = logging.getLogger(__name__)
+files_get_schema = {
+ "type": "object",
+ "properties": {
+ "page": {"type": "string", "minLength": 1},
+ "folder": {"type": "string"},
+ "unzip": {"type": "boolean", "default": "True"},
+ },
+ "additionalProperties": False,
+ "minProperties": 1,
+}
+
+
+class ApiImportFilesIndexHandler(BaseApiHandler):
+ def post(self):
+ auth_data = self.authenticate_user()
+ if not auth_data:
+ return
+
+ if (
+ EnumPermissionsCrafty.SERVER_CREATION
+ not in self.controller.crafty_perms.get_crafty_permissions_list(
+ auth_data[4]["user_id"]
+ )
+ and not auth_data[4]["superuser"]
+ ):
+ # if the user doesn't have Files or Backup permission, return an error
+ return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
+
+ try:
+ data = json.loads(self.request.body)
+ except json.decoder.JSONDecodeError as e:
+ return self.finish_json(
+ 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
+ )
+ try:
+ validate(data, files_get_schema)
+ except ValidationError as e:
+ return self.finish_json(
+ 400,
+ {
+ "status": "error",
+ "error": "INVALID_JSON_SCHEMA",
+ "error_data": str(e),
+ },
+ )
+ # TODO: limit some columns for specific permissions?
+ folder = data["folder"]
+ user_id = auth_data[4]["user_id"]
+ root_path = False
+ if data["unzip"]:
+ if Helpers.check_file_exists(folder):
+ folder = self.file_helper.unzip_server(folder, user_id)
+ root_path = True
+ else:
+ if user_id:
+ user_lang = self.controller.users.get_user_lang_by_id(user_id)
+ self.helper.websocket_helper.broadcast_user(
+ user_id,
+ "send_start_error",
+ {
+ "error": self.helper.translation.translate(
+ "error", "no-file", user_lang
+ )
+ },
+ )
+ else:
+ if not self.helper.check_path_exists(folder):
+ if user_id:
+ user_lang = self.controller.users.get_user_lang_by_id(user_id)
+ self.helper.websocket_helper.broadcast_user(
+ user_id,
+ "send_start_error",
+ {
+ "error": self.helper.translation.translate(
+ "error", "no-file", user_lang
+ )
+ },
+ )
+ return_json = {
+ "root_path": {
+ "path": folder,
+ "top": root_path,
+ }
+ }
+
+ dir_list = []
+ unsorted_files = []
+ file_list = os.listdir(folder)
+ for item in file_list:
+ if os.path.isdir(os.path.join(folder, item)):
+ dir_list.append(item)
+ else:
+ unsorted_files.append(item)
+ file_list = sorted(dir_list, key=str.casefold) + sorted(
+ unsorted_files, key=str.casefold
+ )
+ for raw_filename in file_list:
+ filename = html.escape(raw_filename)
+ rel = os.path.join(folder, raw_filename)
+ dpath = os.path.join(folder, filename)
+ dpath = self.helper.wtol_path(dpath)
+ if os.path.isdir(rel):
+ return_json[filename] = {
+ "path": dpath,
+ "dir": True,
+ }
+ else:
+ return_json[filename] = {
+ "path": dpath,
+ "dir": False,
+ }
+ self.finish_json(200, {"status": "ok", "data": return_json})
diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py
index d2b047d7..7b56220b 100644
--- a/app/classes/web/tornado_handler.py
+++ b/app/classes/web/tornado_handler.py
@@ -48,13 +48,14 @@ class Webserver:
controller: Controller
helper: Helpers
- def __init__(self, helper, controller, tasks_manager):
+ def __init__(self, helper, controller, tasks_manager, file_helper):
self.ioloop = None
self.http_server = None
self.https_server = None
self.helper = helper
self.controller = controller
self.tasks_manager = tasks_manager
+ self.file_helper = file_helper
self._asyncio_patch()
@staticmethod
@@ -146,6 +147,7 @@ class Webserver:
"controller": self.controller,
"tasks_manager": self.tasks_manager,
"translator": self.helper.translation,
+ "file_helper": self.file_helper,
}
handlers = [
(r"/", DefaultHandler, handler_args),
diff --git a/app/classes/web/websocket_handler.py b/app/classes/web/websocket_handler.py
index 78b33951..a2e7f5a4 100644
--- a/app/classes/web/websocket_handler.py
+++ b/app/classes/web/websocket_handler.py
@@ -18,12 +18,18 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
io_loop = None
def initialize(
- self, helper=None, controller=None, tasks_manager=None, translator=None
+ self,
+ helper=None,
+ controller=None,
+ tasks_manager=None,
+ translator=None,
+ file_helper=None,
):
self.helper = helper
self.controller = controller
self.tasks_manager = tasks_manager
self.translator = translator
+ self.file_helper = file_helper
self.io_loop = tornado.ioloop.IOLoop.current()
def get_remote_ip(self):
diff --git a/main.py b/main.py
index 2338517b..8ba07b6a 100644
--- a/main.py
+++ b/main.py
@@ -171,7 +171,7 @@ if __name__ == "__main__":
Console.info("Remote change complete.")
import3 = Import3(helper, controller)
- tasks_manager = TasksManager(helper, controller)
+ tasks_manager = TasksManager(helper, controller, file_helper)
tasks_manager.start_webserver()
def signal_handler(signum, _frame):