diff options
| author | Lukasz Kasprzak <lukasz.kasprzak@pm.me> | 2026-04-13 17:37:26 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukasz.kasprzak@pm.me> | 2026-04-13 17:37:26 +0200 |
| commit | 51d43498b07dc97d795947964534f0903cd05db5 (patch) | |
| tree | 735712bd50b5c244ef922a3b873c709ecce604cd /profanity-aesgcm-handler | |
| parent | 39711cf6c2ec5a3b4480dcb4800cc3802bda5bf2 (diff) | |
| download | bin-51d43498b07dc97d795947964534f0903cd05db5.tar.gz bin-51d43498b07dc97d795947964534f0903cd05db5.zip | |
routine backup
Diffstat (limited to 'profanity-aesgcm-handler')
| -rwxr-xr-x | profanity-aesgcm-handler | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/profanity-aesgcm-handler b/profanity-aesgcm-handler new file mode 100755 index 0000000..0ea5e19 --- /dev/null +++ b/profanity-aesgcm-handler @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import urllib.parse +from pathlib import Path + +import requests +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + + +home = Path.home() +if (home / "downloads").exists(): + DOWNLOAD_DIR = home / "downloads" / "xmpp" + DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True) +else: + DOWNLOAD_DIR = home / "Downloads" / "xmpp" + DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True) + +def log(msg: str) -> None: + log_file = Path.home() / ".local" / "share" / "profanity-aesgcm" / "handler.log" + log_file.parent.mkdir(parents=True, exist_ok=True) + with log_file.open("a", encoding="utf-8") as f: + f.write(msg + "\n") + + +def extract_uri(message: str) -> str | None: + m = re.search(r'(aesgcm://\S+)', message) + return m.group(1) if m else None + + +def parse_aesgcm_uri(uri: str): + parsed = urllib.parse.urlparse(uri) + + if parsed.scheme != "aesgcm": + raise ValueError("Unsupported scheme") + + fragment = parsed.fragment.strip() + frag_hex = "".join(fragment.split()) # remove accidental spaces/newlines + + # XEP-0454 style: 12-byte IV (24 hex chars) + 32-byte key (64 hex chars) + if len(frag_hex) < 88: + raise ValueError(f"Fragment too short: {len(frag_hex)} hex chars") + + iv_hex = frag_hex[:24] + key_hex = frag_hex[24:88] + + iv = bytes.fromhex(iv_hex) + key = bytes.fromhex(key_hex) + + https_url = urllib.parse.urlunparse(("https", parsed.netloc, parsed.path, "", "", "")) + filename = os.path.basename(parsed.path) or "download.bin" + + return https_url, filename, key, iv + + +def download_file(url: str) -> bytes: + r = requests.get(url, timeout=60) + r.raise_for_status() + return r.content + + +def decrypt_xmpp_aesgcm(data: bytes, key: bytes, iv: bytes) -> bytes: + # AESGCM.decrypt expects ciphertext || tag + aesgcm = AESGCM(key) + return aesgcm.decrypt(iv, data, None) + + +def unique_path(path: Path) -> Path: + if not path.exists(): + return path + + stem = path.stem + suffix = path.suffix + + for i in range(1, 1000): + candidate = path.with_name(f"{stem}-{i}{suffix}") + if not candidate.exists(): + return candidate + + raise RuntimeError("Could not create unique filename") + +def main(): + if len(sys.argv) < 2: + sys.exit(0) + + message = sys.argv[1] + uri = extract_uri(message) + if not uri: + sys.exit(0) + + try: + https_url, filename, key, iv = parse_aesgcm_uri(uri) + enc = download_file(https_url) + plain = decrypt_xmpp_aesgcm(enc, key, iv) + + out_path = unique_path(DOWNLOAD_DIR / filename) + out_path.write_bytes(plain) + + log(f"OK saved: {out_path}") + print(str(out_path)) + + # Optional: uncomment if you want auto-open on desktop + # os.system(f'xdg-open "{out_path}" >/dev/null 2>&1 &') + + except Exception as e: + log(f"ERROR: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() |
