Move path sanitization before directory handling in extractFile so
directory entries with "../" prefixes are rejected the same way file
entries already are. Add the same sanitization to extractSevenZipMultiPart
and both zip extraction loops in the Windows updater, which had no path
validation at all.
A failed download has no usable output file, so error notifications now
carry no buttons at all; only the done event offers open file / open
folder actions.
Terminal task notifications now carry actionable buttons and respond to
clicks instead of being inert:
- Done notifications offer Open File and Open Folder for single-file
tasks, and Open Folder only for folder tasks (e.g. multi-file
torrents), following the same rule as the task details page. Error
notifications offer Open Folder only, since the output is rarely
usable.
- Clicking the notification body brings the app window back to the
front (show + focus), covering the close-to-tray case.
- The click target is snapshotted into the notification (Windows: action
arguments; macOS/Linux: notification payload) so buttons keep working
after the task record is deleted, and a missing file is logged instead
of surfacing an OS error dialog.
- macOS resolves buttons through three pre-registered notification
categories (done single-file / done folder / error); Windows uses the
actions API of the pinned notification plugin fork; Linux uses
freedesktop notification actions.
- New openFolder label localized in all 22 ARB catalogs.
Cold-start activation is intentionally out of scope: when the process is
not running, OS activation opens the main window without dispatching the
button action.
Address the completion review findings on top of the upstream review
fixes:
- Store runs on the engine's checkpoint goroutine without the task
lock and serializes the returned value afterwards: take the fetcher
mutex and return a deep copy instead of the live state pointer,
matching the HTTP protocol's Store pattern
- Load the journal and per-run base counters before publishing the run
as active, so Stats/Progress never observe half-initialized fields
- Bind the journal fingerprint to the key IV as well: a rotated IV on
an otherwise identical plan would silently decrypt to garbage
- Keep every EXT-X-MEDIA member per group instead of only the first
one, so a group with a URI on a later member is still detected
- After a merge that raced with a pause, keep the staged segments and
exit silently instead of cleaning them up; drop a dead context
check and an unused helper, and make the MAP identity and merge
part-file names unambiguous
The merge pipeline concatenates stored bytes, so features it cannot
reproduce faithfully are now detected and rejected with a clear error
instead of silently succeeding with a broken output:
- EXT-X-GAP (a hole in the merged file) and EXT-X-DISCONTINUITY
(a broken timeline)
- separate audio renditions referenced by the selected variant: the
master EXT-X-MEDIA groups are parsed and an AUDIO group with its own
URI fails the resolve; unreferenced groups are ignored
- changing init sections: a repeated EXT-X-MAP is compared by all of
its attributes (URI and BYTERANGE), not just the URI
- encrypted EXT-X-MAP without an explicit IV: the spec does not define
a sequence-derived IV for init sections
- non-identity AES-128 KEYFORMAT values
The supported matrix is documented in the package doc.
Add regression tests for every rejection plus real media validation:
ffmpeg-generated TS segments and fMP4 init/fragments are committed as
fixtures, downloaded through the fetcher (including an AES-128 key
rotation variant), and the merged outputs are validated structurally -
TS on the 188 byte grid with continuity counter rules, PAT/PMT and PES
presence, fMP4 by walking the ISO-BMFF box hierarchy.
- Give every task a random staging identity persisted in the saved
state: two tasks downloading the same playlist into the same
directory get separate segment folders and journals instead of
deleting each other's progress
- Bind the journal to a fingerprint of the full download plan (media
URL, ordered segments, ranges, keys); foreign or unreadable journals
are discarded rather than applied to the wrong segments, and the
ledger is rewritten through a temp file + rename
- Legacy states without a staging identity migrate to a fresh folder
instead of reusing the shared URL-hash folder
- Merge into a unique temporary file next to the target, check write
and close errors, then remove-then-rename over the final name: a
failed merge preserves the staged segments for a retry and re-merging
after a crash stays idempotent
- Drop the staging folder when a URL patch invalidates the plan
- Parse the request extra in Setup so restored fetchers resend Referer,
Cookie and other configured headers on resumed segment/key requests;
the store/restore regression test round-trips the state and the task
meta through JSON exactly like the engine storage does
- Rebuild the resolved plan on Start when the resource survived but the
plan was lost to a crash between the task save and the first
checkpoint, keeping the existing output name
- Patch now merges partial updates instead of replacing the request and
options wholesale: URL-only patches keep the output path and renamed
file, header-only patches keep the resolved plan and take effect on
the next run, Labels merge per key, Proxy swaps rebuild the client,
and nil arguments are no longer a panic. Editing a running task is
rejected, consistent with the ed2k protocol
- Cover retry-after-failure counter resets, running-task patch
rejection and the missing-state self-heal with regression tests
- Give every Start its own run context, WaitGroup (including the queue
producer), supervisor done channel and snapshots of the plan, request
headers, HTTP client and merge target; Pause/Close wait for all run
goroutines, including a merge in flight, before returning
- Build the HTTP client in Resolve/Start before workers launch instead of
lazily inside do(), which raced between concurrent segment downloads
- Reject task edits while a run is active instead of mutating shared state
- Reset counters per run and seed them from the completed-segment journal;
count only stored output bytes as progress, never discarded retry bytes
- Report Res.Size only when every segment is unencrypted with a known
length; ciphertext sums overstate the final output, indeterminate plans
rely on the engine's TotalDownloaded backfill at completion