import { app, shell, type BrowserWindow } from 'electron' import { existsSync, openSync, readSync, closeSync } from 'fs' import { join } from 'path' import { assertHttpUrl } from './url' import { parseUrlShortcutContent } from '@shared/ipc' /** Only ever forward http(s) targets into the app — same restriction the * external-link window-open handler in index.ts applies to in-page links. * Delegates to the download path's guard so an incoming deep-link target is * validated AND parser-normalised (no embedded tabs/newlines/control chars * reach the renderer); returns null instead of throwing for the argv scan. * (audit T3 / F5) */ function asHttpUrl(candidate: string): string | null { try { return assertHttpUrl(candidate) } catch { return null } } /** Reads a Windows Internet Shortcut (.url) file's target — what Explorer's * "Send to" menu hands us when the user sends a saved link to AeroFetch. * Only the first 64 KB is read: a real shortcut is a few hundred bytes, so this * caps memory and limits exposure if argv points at a pathological or oversized * file that merely ends in `.url`. (audit T5) */ const MAX_URL_FILE_BYTES = 64 * 1024 function readUrlShortcut(path: string): string | null { let fd: number | null = null try { fd = openSync(path, 'r') const buf = Buffer.alloc(MAX_URL_FILE_BYTES) const bytes = readSync(fd, buf, 0, buf.length, 0) const url = parseUrlShortcutContent(buf.toString('utf8', 0, bytes)) return url ? asHttpUrl(url) : null } catch { return null } finally { if (fd !== null) closeSync(fd) } } /** * Pulls a download target out of process argv — either the `aerofetch://` * custom protocol (`aerofetch://download?url=`) or a `.url` Internet * Shortcut path Explorer's "Send to" menu passes us (see registerSendToShortcut). */ export function extractIncomingUrl(argv: string[]): string | null { for (const arg of argv) { if (/^aerofetch:\/\//i.test(arg)) { try { const target = new URL(arg).searchParams.get('url') const valid = target && asHttpUrl(target) if (valid) return valid } catch { /* malformed protocol invocation — ignore */ } } else if (/\.url$/i.test(arg) && existsSync(arg)) { const target = readUrlShortcut(arg) if (target) return target } } return null } /** * Adds/refreshes a "Send to AeroFetch" entry in Explorer's SendTo menu — the * closest Windows analog to Android's share sheet a Win32 (non-MSIX) app can * offer; registering as an actual Share Target requires an MSIX package. * Best-effort and idempotent (re-running just overwrites the same shortcut), * so a failure here should never block startup. */ export function registerSendToShortcut(): void { if (process.platform !== 'win32') return try { const shortcutPath = join( app.getPath('appData'), 'Microsoft', 'Windows', 'SendTo', 'AeroFetch.lnk' ) shell.writeShortcutLink(shortcutPath, { target: process.execPath, description: 'Send to AeroFetch', icon: process.execPath, iconIndex: 0 }) } catch { /* SendTo integration is a nice-to-have */ } } /** Focuses (and un-minimizes) an existing window — used when a second launch * (protocol or SendTo) should hand its URL to the already-running instance. */ export function focusWindow(win: BrowserWindow): void { if (win.isMinimized()) win.restore() win.show() win.focus() }