mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-22 01:14:52 +02:00
Update to v8.21.0
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
# coding: utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
if "/www/server/panel" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel")
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
|
||||
import public
|
||||
|
||||
from mod.project.project_import.core.api_utils import error_response
|
||||
from mod.project.project_import.core.constants import MAX_UPLOAD_SIZE, locks_dir, new_id
|
||||
from mod.project.project_import.core.exceptions import ProjectImportError
|
||||
from mod.project.project_import.core.json_store import file_lock
|
||||
from mod.project.project_import.core.security import ensure_child_path
|
||||
from mod.project.project_import.core.session_store import SessionStore
|
||||
|
||||
|
||||
ALLOWED_ARCHIVES = (".zip", ".tar", ".tar.gz", ".tgz")
|
||||
|
||||
|
||||
class main:
|
||||
def __init__(self):
|
||||
self.sessions = SessionStore()
|
||||
|
||||
def upload(self, get):
|
||||
"""Wrap files.upload with a session-scoped fixed destination."""
|
||||
try:
|
||||
from BTPanel import request
|
||||
from files import files as PanelFiles
|
||||
|
||||
file_name = os.path.basename(str(get.get("f_name", "")).strip())
|
||||
file_size = int(get.get("f_size", 0) or 0)
|
||||
offset = int(get.get("f_start", 0) or 0)
|
||||
if not file_name or not file_name.lower().endswith(ALLOWED_ARCHIVES):
|
||||
raise ProjectImportError("Unsupported archive format", "UNSUPPORTED_ARCHIVE")
|
||||
if file_size <= 0 or file_size > MAX_UPLOAD_SIZE:
|
||||
raise ProjectImportError("Invalid upload file size", "UPLOAD_SIZE_INVALID")
|
||||
if offset < 0 or offset > file_size:
|
||||
raise ProjectImportError("Invalid upload offset", "UPLOAD_OFFSET_INVALID")
|
||||
if not request.files.getlist("blob"):
|
||||
raise ProjectImportError("Upload chunk is missing", "UPLOAD_CHUNK_REQUIRED")
|
||||
|
||||
session_id = str(get.get("session_id", "")).strip()
|
||||
if session_id:
|
||||
session = self.sessions.get(session_id)
|
||||
if session.get("source_type") != "archive":
|
||||
raise ProjectImportError("This session is not an archive session", "INVALID_SOURCE_TYPE")
|
||||
else:
|
||||
if offset != 0:
|
||||
raise ProjectImportError(
|
||||
"session_id is required after the first upload chunk",
|
||||
"UPLOAD_SESSION_REQUIRED",
|
||||
)
|
||||
session = self.sessions.create("archive")
|
||||
session_id = session["session_id"]
|
||||
|
||||
upload_dir = os.path.join(self.sessions.work_dir(session_id), "upload")
|
||||
os.makedirs(upload_dir, mode=0o700, exist_ok=True)
|
||||
temporary_path = ensure_child_path(
|
||||
upload_dir,
|
||||
os.path.join(upload_dir, file_name + "." + str(file_size) + ".upload.tmp"),
|
||||
)
|
||||
final_path = ensure_child_path(upload_dir, os.path.join(upload_dir, file_name))
|
||||
lock_path = os.path.join(locks_dir(), session_id + ".upload.lock")
|
||||
|
||||
with file_lock(lock_path):
|
||||
session = self.sessions.get(session_id)
|
||||
upload = session.get("upload", {})
|
||||
same_file = (
|
||||
upload.get("file_name") == file_name
|
||||
and int(upload.get("file_size", 0) or 0) == file_size
|
||||
)
|
||||
if (
|
||||
same_file
|
||||
and upload.get("completed")
|
||||
and os.path.isfile(final_path)
|
||||
and os.path.getsize(final_path) == file_size
|
||||
and offset == file_size
|
||||
):
|
||||
return public.return_message(0, 0, self._result(session_id, upload))
|
||||
|
||||
if not same_file or upload.get("completed"):
|
||||
if offset != 0:
|
||||
expected = file_size if same_file and upload.get("completed") else 0
|
||||
raise ProjectImportError(
|
||||
"Unexpected upload offset; expected {}".format(expected),
|
||||
"UPLOAD_OFFSET_MISMATCH",
|
||||
)
|
||||
self._remove_previous_upload(upload_dir, upload)
|
||||
upload = {
|
||||
"upload_id": new_id("piu"),
|
||||
"file_name": file_name,
|
||||
"file_size": file_size,
|
||||
"uploaded_size": 0,
|
||||
"completed": False,
|
||||
"part_path": temporary_path,
|
||||
"path": final_path,
|
||||
}
|
||||
|
||||
def initialize(data):
|
||||
data["upload"] = dict(upload)
|
||||
data["status"] = "uploading"
|
||||
return data
|
||||
|
||||
self.sessions.update(session_id, initialize)
|
||||
|
||||
current_size = os.path.getsize(temporary_path) if os.path.isfile(temporary_path) else 0
|
||||
if current_size != offset:
|
||||
raise ProjectImportError(
|
||||
"Unexpected upload offset; expected {}".format(current_size),
|
||||
"UPLOAD_OFFSET_MISMATCH",
|
||||
)
|
||||
|
||||
args = public.to_dict_obj({
|
||||
"f_path": upload_dir,
|
||||
"f_name": file_name,
|
||||
"f_size": file_size,
|
||||
"f_start": offset,
|
||||
})
|
||||
try:
|
||||
upstream = PanelFiles().upload(args)
|
||||
except Exception:
|
||||
self._rollback_partial(temporary_path, current_size)
|
||||
raise
|
||||
|
||||
completed = os.path.isfile(final_path) and os.path.getsize(final_path) == file_size
|
||||
if completed:
|
||||
uploaded_size = file_size
|
||||
else:
|
||||
uploaded_size = os.path.getsize(temporary_path) if os.path.isfile(temporary_path) else 0
|
||||
if uploaded_size > file_size:
|
||||
self._rollback_partial(temporary_path, current_size)
|
||||
raise ProjectImportError(
|
||||
"Uploaded data exceeds declared size",
|
||||
"UPLOAD_SIZE_EXCEEDED",
|
||||
)
|
||||
if type(upstream) is not int:
|
||||
self._rollback_partial(temporary_path, current_size)
|
||||
raise ProjectImportError(
|
||||
self._upstream_error(upstream),
|
||||
"FILE_UPLOAD_FAILED",
|
||||
)
|
||||
|
||||
def save_progress(data):
|
||||
data["upload"] = dict(upload)
|
||||
data["upload"]["uploaded_size"] = uploaded_size
|
||||
data["upload"]["completed"] = completed
|
||||
if completed:
|
||||
data["upload"].pop("part_path", None)
|
||||
data["status"] = "created"
|
||||
else:
|
||||
data["status"] = "uploading"
|
||||
return data
|
||||
|
||||
saved = self.sessions.update(session_id, save_progress)
|
||||
return public.return_message(0, 0, self._result(session_id, saved["upload"]))
|
||||
except Exception as exc:
|
||||
return error_response(exc)
|
||||
|
||||
@staticmethod
|
||||
def _result(session_id, upload):
|
||||
uploaded_size = int(upload.get("uploaded_size", 0) or 0)
|
||||
return {
|
||||
"session_id": session_id,
|
||||
"upload_id": upload.get("upload_id", ""),
|
||||
"file_name": upload.get("file_name", ""),
|
||||
"file_size": int(upload.get("file_size", 0) or 0),
|
||||
"uploaded_size": uploaded_size,
|
||||
"next_start": uploaded_size,
|
||||
"completed": bool(upload.get("completed")),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _rollback_partial(path, size):
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
try:
|
||||
with open(path, "r+b") as handle:
|
||||
handle.truncate(max(0, int(size)))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _upstream_error(result):
|
||||
if isinstance(result, dict):
|
||||
message = result.get("message", result.get("msg", result.get("result", "")))
|
||||
if isinstance(message, dict):
|
||||
message = message.get("result", message.get("error", ""))
|
||||
if message:
|
||||
return str(message)
|
||||
return "File upload failed"
|
||||
|
||||
@staticmethod
|
||||
def _remove_previous_upload(upload_dir, upload):
|
||||
for field in ("part_path", "path"):
|
||||
path = str(upload.get(field, ""))
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
path = ensure_child_path(upload_dir, path)
|
||||
except ProjectImportError:
|
||||
continue
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError:
|
||||
pass
|
||||
Reference in New Issue
Block a user