143 lines
4.6 KiB
Python
143 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Create a sanitized BitBrowser database while preserving extensions and kernels."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sqlite3
|
|
from pathlib import Path
|
|
|
|
|
|
def quote_identifier(value: str) -> str:
|
|
return '"' + value.replace('"', '""') + '"'
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source", required=True)
|
|
parser.add_argument("--output", required=True)
|
|
args = parser.parse_args()
|
|
|
|
source_path = Path(args.source).resolve()
|
|
output_path = Path(args.output).resolve()
|
|
if source_path == output_path:
|
|
raise RuntimeError("The output database must differ from the source database")
|
|
if not source_path.is_file():
|
|
raise FileNotFoundError(source_path)
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
if output_path.exists():
|
|
output_path.unlink()
|
|
|
|
source = sqlite3.connect(f"file:{source_path.as_posix()}?mode=ro", uri=True)
|
|
target = sqlite3.connect(str(output_path))
|
|
try:
|
|
source.backup(target)
|
|
target.execute("PRAGMA foreign_keys = OFF")
|
|
|
|
tables = [
|
|
row[0]
|
|
for row in target.execute(
|
|
"SELECT name FROM sqlite_master "
|
|
"WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
|
)
|
|
]
|
|
|
|
extension_rows = []
|
|
if "local_extensions" in tables:
|
|
extension_rows = target.execute(
|
|
"SELECT name, desc, extension_id, icon, version, is_use "
|
|
"FROM local_extensions ORDER BY id"
|
|
).fetchall()
|
|
|
|
chrome_rows = []
|
|
if "chrome_versions" in tables:
|
|
chrome_rows = target.execute(
|
|
"SELECT chrome, version FROM chrome_versions ORDER BY id"
|
|
).fetchall()
|
|
|
|
firefox_rows = []
|
|
if "firefox_versions" in tables:
|
|
firefox_rows = target.execute(
|
|
"SELECT firefox, version FROM firefox_versions ORDER BY id"
|
|
).fetchall()
|
|
|
|
for table in tables:
|
|
target.execute(f"DELETE FROM {quote_identifier(table)}")
|
|
target.execute("DELETE FROM sqlite_sequence")
|
|
|
|
if extension_rows:
|
|
target.executemany(
|
|
"INSERT INTO local_extensions "
|
|
"(name, desc, extension_id, icon, version, is_use, cloud_id, main_user_id) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)",
|
|
extension_rows,
|
|
)
|
|
if chrome_rows:
|
|
target.executemany(
|
|
"INSERT INTO chrome_versions (chrome, version) VALUES (?, ?)",
|
|
chrome_rows,
|
|
)
|
|
if firefox_rows:
|
|
target.executemany(
|
|
"INSERT INTO firefox_versions (firefox, version) VALUES (?, ?)",
|
|
firefox_rows,
|
|
)
|
|
|
|
target.commit()
|
|
target.execute("VACUUM")
|
|
target.execute("PRAGMA optimize")
|
|
|
|
counts = {
|
|
table: target.execute(
|
|
f"SELECT COUNT(*) FROM {quote_identifier(table)}"
|
|
).fetchone()[0]
|
|
for table in tables
|
|
}
|
|
allowed_nonempty = {"local_extensions", "chrome_versions", "firefox_versions"}
|
|
unexpected = {
|
|
table: count
|
|
for table, count in counts.items()
|
|
if count and table not in allowed_nonempty
|
|
}
|
|
if unexpected:
|
|
raise RuntimeError(f"Sanitization failed; unexpected rows remain: {unexpected}")
|
|
|
|
integrity = target.execute("PRAGMA integrity_check").fetchone()[0]
|
|
if integrity != "ok":
|
|
raise RuntimeError(f"SQLite integrity check failed: {integrity}")
|
|
|
|
report = {
|
|
"source": str(source_path),
|
|
"output": str(output_path),
|
|
"extensions": [
|
|
{
|
|
"name": row[0],
|
|
"extension_id": row[2],
|
|
"version": row[4],
|
|
"enabled": bool(row[5]),
|
|
}
|
|
for row in extension_rows
|
|
],
|
|
"chrome_versions": [
|
|
{"chrome": row[0], "version": row[1]} for row in chrome_rows
|
|
],
|
|
"firefox_versions": [
|
|
{"firefox": row[0], "version": row[1]} for row in firefox_rows
|
|
],
|
|
"table_counts": counts,
|
|
"integrity_check": integrity,
|
|
"size_bytes": os.path.getsize(output_path),
|
|
}
|
|
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
finally:
|
|
target.close()
|
|
source.close()
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|