`transcribe_file` already had `ensure_main_window`, but accepted an
arbitrary `path: String` and fed it straight to
`lumotia_audio::decode_audio_file_limited`. The OS file picker
typically constrains the user's path, but the IPC surface itself never
checked: a compromised webview could point the decoder at a 50 GiB
sparse file (OOM the worker), or a deliberately-malformed blob with an
extension chosen to provoke a parser bug in Symphonia.
This change adds defence-in-depth:
- extension allowlist (`wav`, `mp3`, `m4a`, `mp4`, `flac`, `ogg`,
`opus`, `webm`, `aac`) matched case-insensitively. Anything else,
including no extension at all, is rejected with a clear error;
- 1 GiB ceiling on the input file. Stats via `std::fs::metadata`
(which resolves symlinks) so the cap sees the real blob, not a
symlink-target lie. The 2-hour duration cap still runs after decode
for the realistic-audio case.
The validation lives in a pure helper, `validate_transcribe_input`, so
the rule can be unit-tested without spawning Tauri or hitting the
decoder.
Eight unit tests cover: accepts plain `.wav`, accepts uppercase `.MP3`,
accepts every allowlisted extension, rejects `.so` payload, rejects
missing extension, rejects oversize file, accepts exactly-at-cap file,
rejects path-traversal with disallowed extension.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>