diff --git a/data/misc/fetch_offline_docs.py b/data/misc/fetch_offline_docs.py index e18261e3..442817cb 100644 --- a/data/misc/fetch_offline_docs.py +++ b/data/misc/fetch_offline_docs.py @@ -1,42 +1,89 @@ #!/usr/bin/env python3 + import html import io import pathlib import shutil import sys import tarfile +import tempfile import urllib.error import urllib.request + url = sys.argv[1] out_dir = pathlib.Path(sys.argv[2]) source_dir = pathlib.Path(sys.argv[3]) if len(sys.argv) > 3 else pathlib.Path.cwd() -docs_dir = out_dir / 'offline-docs' +docs_dir = out_dir / "offline-docs" + if docs_dir.exists(): shutil.rmtree(docs_dir) docs_dir.mkdir(parents=True, exist_ok=True) -def write_source_docs(): - parts = ['
{html.escape(path.read_text(encoding="utf-8", errors="replace"))}')
- parts.append('')
- (docs_dir / 'index.html').write_text('\n'.join(parts), encoding='utf-8')
+ parts.append(
+ f"{html.escape(path.read_text(encoding='utf-8', errors='replace'))}"
+ )
+
+ parts.append("")
+ (docs_dir / "index.html").write_text("\n".join(parts), encoding="utf-8")
+
+
+def safe_extract(tar: tarfile.TarFile, target: pathlib.Path) -> None:
+ target = target.resolve()
+
+ for member in tar.getmembers():
+ member_path = (target / member.name).resolve()
+ if not str(member_path).startswith(str(target) + "/"):
+ raise tarfile.TarError(f"unsafe archive path: {member.name}")
+
+ tar.extractall(target)
+
+
+def copy_index_tree(extracted_dir: pathlib.Path) -> bool:
+ indexes = sorted(extracted_dir.rglob("index.html"))
+ if not indexes:
+ return False
+
+ root = indexes[0].parent
+ for item in root.iterdir():
+ dest = docs_dir / item.name
+ if item.is_dir():
+ shutil.copytree(item, dest, dirs_exist_ok=True)
+ else:
+ shutil.copy2(item, dest)
+
+ return (docs_dir / "index.html").exists()
+
if url:
try:
- with urllib.request.urlopen(url, timeout=30) as r:
- data = r.read()
- with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as tar:
- try:
- tar.extractall(docs_dir, filter='data')
- except TypeError:
- tar.extractall(docs_dir)
+ with urllib.request.urlopen(url, timeout=30) as response:
+ data = response.read()
+
+ with tempfile.TemporaryDirectory() as tmp:
+ extract_dir = pathlib.Path(tmp)
+ with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as tar:
+ safe_extract(tar, extract_dir)
+
+ if not copy_index_tree(extract_dir):
+ write_source_docs()
+
except (OSError, tarfile.TarError, urllib.error.URLError) as error:
- print(f'offline docs download failed: {error}', file=sys.stderr)
+ print(f"offline docs download failed: {error}", file=sys.stderr)
write_source_docs()
else:
write_source_docs()
-(out_dir / 'offline-docs.stamp').write_text('ok')
+
+(out_dir / "offline-docs.stamp").write_text("ok", encoding="utf-8")
diff --git a/flatpak/net.zoite.Zoitechat.json b/flatpak/net.zoite.Zoitechat.json
index 9b37e0ad..477797d9 100644
--- a/flatpak/net.zoite.Zoitechat.json
+++ b/flatpak/net.zoite.Zoitechat.json
@@ -57,7 +57,8 @@
"-Dwith-perl=perl",
"-Dwith-python=python3",
"-Dwith-lua=lua",
- "-Doffline-docs-package=net.zoite.Zoitechat"
+ "-Doffline-docs-package=net.zoite.Zoitechat",
+ "-Doffline-docs-dir=share/zoitechat/offline-docs"
],
"build-options": {
"cflags": "-Wno-error=missing-include-dirs"
diff --git a/meson_options.txt b/meson_options.txt
index 63bc81b1..3559db56 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -63,12 +63,12 @@ option('with-perl-legacy-api', type: 'boolean', value: false,
description: 'Enables the legacy IRC perl module for compatibility with old scripts'
)
-option('offline-docs-url', type: 'string', value: '',
+option('offline-docs-url', type: 'string', value: 'https://dl.zoitechat.org/offlinedocs/zoitechat-docs-html-2.18.1.tar.gz',
description: 'URL for offline documentation archive (tar.gz)'
)
option('offline-docs-package', type: 'string', value: '',
description: 'Package or app id to use under the installed documentation directory'
)
-option('offline-docs-dir', type: 'string', value: 'https://dl.zoitechat.org/offlinedocs/zoitechat-docs-html-2.18.1.tar.gz',
+option('offline-docs-dir', type: 'string', value: '',
description: 'Installed offline documentation directory relative to prefix'
)