|
2 | 2 | import json
|
3 | 3 | import os
|
4 | 4 | import re
|
| 5 | +import subprocess # nosec B404 |
| 6 | +import sys |
5 | 7 | import tempfile
|
6 | 8 | import zipfile
|
7 | 9 | from io import BytesIO
|
8 |
| -from multiprocessing import Process |
| 10 | +from pathlib import Path |
9 | 11 |
|
10 | 12 | import dataset
|
11 |
| -from flask import copy_current_request_context |
12 | 13 | from flask import current_app as app
|
13 | 14 | from flask_migrate import upgrade as migration_upgrade
|
14 | 15 | from sqlalchemy.engine.url import make_url
|
|
24 | 25 | from CTFd.plugins.migrations import upgrade as plugin_upgrade
|
25 | 26 | from CTFd.utils import get_app_config, set_config, string_types
|
26 | 27 | from CTFd.utils.dates import unix_time
|
| 28 | +from CTFd.utils.exports.databases import is_database_mariadb |
27 | 29 | from CTFd.utils.exports.freeze import freeze_export
|
28 | 30 | from CTFd.utils.migrations import (
|
29 | 31 | create_database,
|
@@ -95,6 +97,10 @@ def set_status(val):
|
95 | 97 | cache.set(key="import_status", value=val, timeout=cache_timeout)
|
96 | 98 | print(val)
|
97 | 99 |
|
| 100 | + # Reset import cache keys and don't print these values |
| 101 | + cache.set(key="import_error", value=None, timeout=cache_timeout) |
| 102 | + cache.set(key="import_status", value=None, timeout=cache_timeout) |
| 103 | + |
98 | 104 | if not zipfile.is_zipfile(backup):
|
99 | 105 | set_error("zipfile.BadZipfile: zipfile is invalid")
|
100 | 106 | raise zipfile.BadZipfile
|
@@ -165,6 +171,7 @@ def set_status(val):
|
165 | 171 | sqlite = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("sqlite")
|
166 | 172 | postgres = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("postgres")
|
167 | 173 | mysql = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("mysql")
|
| 174 | + mariadb = is_database_mariadb() |
168 | 175 |
|
169 | 176 | if erase:
|
170 | 177 | set_status("erasing")
|
@@ -258,7 +265,7 @@ def insertion(table_filenames):
|
258 | 265 | table = side_db[table_name]
|
259 | 266 |
|
260 | 267 | saved = json.loads(data)
|
261 |
| - count = saved["count"] |
| 268 | + count = len(saved["results"]) |
262 | 269 | for i, entry in enumerate(saved["results"]):
|
263 | 270 | set_status(f"inserting {member} {i}/{count}")
|
264 | 271 | # This is a hack to get SQLite to properly accept datetime values from dataset
|
@@ -306,6 +313,23 @@ def insertion(table_filenames):
|
306 | 313 | if requirements and isinstance(requirements, string_types):
|
307 | 314 | entry["requirements"] = json.loads(requirements)
|
308 | 315 |
|
| 316 | + # From v3.1.0 to v3.5.0 FieldEntries could have been varying levels of JSON'ified strings. |
| 317 | + # For example "\"test\"" vs "test". This results in issues with importing backups between |
| 318 | + # databases. Specifically between MySQL and MariaDB. Because CTFd standardizes against MySQL |
| 319 | + # we need to have an edge case here. |
| 320 | + if member == "db/field_entries.json": |
| 321 | + value = entry.get("value") |
| 322 | + if value: |
| 323 | + try: |
| 324 | + # Attempt to convert anything to its original Python value |
| 325 | + entry["value"] = str(json.loads(value)) |
| 326 | + except (json.JSONDecodeError, TypeError): |
| 327 | + pass |
| 328 | + finally: |
| 329 | + # Dump the value into JSON if its mariadb or skip the conversion if not mariadb |
| 330 | + if mariadb: |
| 331 | + entry["value"] = json.dumps(entry["value"]) |
| 332 | + |
309 | 333 | try:
|
310 | 334 | table.insert(entry)
|
311 | 335 | except ProgrammingError:
|
@@ -413,9 +437,11 @@ def insertion(table_filenames):
|
413 | 437 |
|
414 | 438 |
|
415 | 439 | def background_import_ctf(backup):
|
416 |
| - @copy_current_request_context |
417 |
| - def ctx_bridge(): |
418 |
| - import_ctf(backup) |
419 |
| - |
420 |
| - p = Process(target=ctx_bridge) |
421 |
| - p.start() |
| 440 | + # The manage.py script will delete the backup for us |
| 441 | + f = tempfile.NamedTemporaryFile(delete=False) |
| 442 | + backup.save(f.name) |
| 443 | + python = sys.executable # Get path of Python interpreter |
| 444 | + manage_py = Path(app.root_path).parent / "manage.py" # Path to manage.py |
| 445 | + subprocess.Popen( # nosec B603 |
| 446 | + [python, manage_py, "import_ctf", "--delete_import_on_finish", f.name] |
| 447 | + ) |
0 commit comments